Protected Registry Desktop App
Protected Registry Desktop App
Section titled “Protected Registry Desktop App”This note captures the current npm registry-related FractalOps surface and the new protected package delivery plan.
Build a desktop application with an embedded CLI surface for protected package install, publish, build hardening, and customer delivery.
The app is not a standalone npm registry. It is a FractalOps client:
- browser access uses OIDC through Pomerium and Keycloak
- package install/publish uses FractalOps-issued short-lived npm tokens
- every token mint is checked against SCIM-projected entitlement
- package consumers receive hardened package artifacts, not raw source
- customer delivery stores protected artifacts inside the project delivery area
Current FractalOps Registry Surface
Section titled “Current FractalOps Registry Surface”FractalOps already has the core registry base:
fractalops/backend/src/fractalops/contexts/access/application/integration/registry_login.pymints Nexus npm tokens and renders.npmrc.fractalops/backend/src/fractalops/cli.pyexposesfractalops registry loginandfractalops registry login-machine.fractalops/backend/src/fractalops/contexts/access/application/integration/credential_broker.pybrokersnexuscredentials with scopes such asinstall,publish, andadmin.fractalops/ops/lxc/assets/nexus-pomerium-plugin/index.jsmaps Pomerium identity, registry JWTs, and service-token identities into Nexus access.fractalops/ops/containers/nexus/builds/releases the Nexus image.fractalops/platform/k8s/apps/nexus/owns the Kubernetes deployment.fractalops-frontend/portal/src/components/organisms/ProjectPackageHub.astroexposes package creation, install-token minting, publish-token minting,.npmrccopy, and registry launch.
Existing model:
principal -> FractalOps session / Pomerium claims / service token -> group_paths and policy roles -> RegistryTokenService -> short-lived npm token -> .npmrc -> Nexus install/publishRequired Change
Section titled “Required Change”Current token minting uses projected group_paths. The protected package
delivery plan must make the entitlement gate explicit:
OIDC identity -> SCIM-projected group and project entitlement -> FractalOps credential policy -> registry token with package/project/action claims -> Nexus plugin validationRequired token claims:
tenant_idproject_slugpackage_namepackage_scopeallowed_actions:install,publish, or bothentitlement_snapshot_idartifact_policy:hardened,encrypted_delivery, or bothexp
The Nexus plugin must reject mismatched package/action usage even if the token is otherwise valid.
Current Implementation Progress
Section titled “Current Implementation Progress”Implemented first backend enforcement slice:
RegistryTokenService.exchange_token()now emits protected registry claims:tenant_id,project_slug,package_name,package_scope,allowed_actions,entitlement_snapshot_id,artifact_policy, andexp.- Protected token minting now requires explicit project entitlement when
project_slug,package_name, oraction_modeis present. PortalCredentialBrokerService.mint()passes tenant/project/package/action context into Nexus npm token issuance.POST /v1/packages/registry/tokens/npmexposes a desktop-app-facing token mint path through the existing credential broker and audit grant pipeline.- The Nexus Pomerium plugin maps registry token claims into internal claim groups and rejects install/publish when the requested package, package scope, or action does not match the token.
apps/protected-registry-desktop/now contains the first desktop-app command layer:protected-registry install <package> --project <slug>protected-registry publish <package> --project <slug>- a FractalOps token client for
POST /v1/packages/registry/tokens/npm - temp
.npmrccreation with cleanup infinally - npm, pnpm, and yarn runner support using per-command userconfig
- Protected package lifecycle endpoints now exist:
POST /v1/packages/protection/buildsPOST /v1/packages/delivery/bundlesGET /v1/packages/delivery/bundles/{bundle_id}GET /v1/packages/delivery/viewPOST /v1/packages/delivery/grants
- Protection build requests record the hardening pipeline contract as audit and evidence metadata.
- Delivery bundle requests store
key_refonly and reject raw key values. - Delivery grants record customer-specific access grants as audit and evidence metadata.
- The delivery view read model lists project/customer/package bundles, grants, and evidence record URLs from the existing audit/evidence resources.
- Desktop app local executors now include:
protected-registry protect-build <package> --project <slug> --version <version> --source <path>protected-registry deliver <package> --project <slug> --customer <slug> --version <version> --artifact <path> --key-ref <uri>- protected build manifest, integrity, SBOM, provenance, and private source-map escrow metadata writers
- dependency-free JavaScript hardener that removes public source-map pointers, strips comments/whitespace, mangles declaration identifiers, and writes a protected artifact tree separate from raw source
- production hardening package selection is now explicit:
terserandjavascript-obfuscatorare optional desktop dependencies for production builds, withbuiltin-js-hardeneras a recorded fallback - AES-256-GCM delivery bundle writer that reads the encryption key from the
local process only and writes
key_refto the manifest - delivery key provider descriptors for
openbao://,aws-kms://,gcp-kms://, andazure-keyvault://references; manifests record provider andreference_onlypolicy without raw key material - OpenBao Transit resolver support for provider-backed bundle encryption; it
sends ciphertext to
/v1/transit/decrypt/<key>and never writes Vault token or unwrapped key material into manifests - OpenBao grant materializer support for
grant-delivery --materialize-grant; it writes a Transit decrypt ACL policy first, then sendskey_grant_refandgrant_materializationmetadata to FractalOps - cloud KMS request boundaries for
aws-kms://,gcp-kms://, andazure-keyvault://refs so provider SDK clients can be plugged in without changing bundle manifests - cloud KMS grant materializer adapters for AWS
CreateGrant, GCP KMS IAM binding writes, and Azure Key Vault access-policy writes; adapters returnkey_grant_refandgrant_materializationmetadata without provider credentials or unwrapped key material protected-registry submit-bundle --manifest <path>to submit encrypted delivery manifests to FractalOpsprotected-registry grant-delivery --bundle-id <id> --customer <slug> --grantee <subject>to request customer-specific delivery grants- FractalOps package lifecycle client for delivery bundle and grant APIs
- Desktop auth/session modules now include:
- OIDC login URL generation for the desktop shell
- OIDC callback parsing with state validation
- loopback callback receiver for browser/desktop login completion
- public session view that redacts token values
- Windows DPAPI-backed secure store adapter for user-scoped session storage
protected-registry login,complete-login,session, andlogoutcommand surfaceprotected-registry login --listenfor local callback capture- protected package commands require an authenticated session vault entry with an access token before install, publish, protect-build, deliver, submit-bundle, or grant-delivery can run
- install/publish token minting reads the OIDC access token from the session vault instead of accepting environment or CLI access-token bypasses
- A first static UI shell exists under
apps/protected-registry-desktop/src/ui/with package, build, delivery, and session surfaces. It is intentionally token-free, renders a login gate as the first screen, and delegates privileged actions to the command/session modules. - Tauri host scaffolding now exists under
apps/protected-registry-desktop/src-tauri/:tauri.conf.jsonloads the static UI shell from../src/ui- Windows package targets are
msiandnsis - Rust host exposes a minimal command hook for secure-store UI integration
- package scripts expose
doctor,tauri:dev, andtauri:build
- A native build readiness doctor exists at
apps/protected-registry-desktop/src/doctor/build-readiness.mjs; it checks Node, Cargo, Tauri config, Rust host, desktop CLI bin wiring, and production hardening package selection before a native build attempt. - Desktop verification scripts now exist:
npm run verifyruns desktop tests and includes the native readiness report without failing the whole check when Cargo is missing.npm run verify:nativeruns the same checks in strict native mode and fails until Cargo/Tauri native readiness is complete.npm run verify:hardeningimportsterserandjavascript-obfuscator, verifies their runtime APIs, and fails if the production build environment would fall back tobuiltin-js-hardener.npm run verify:native-artifactschecks that a Tauri build produced both MSI and NSIS installer artifacts undersrc-tauri/target/release/bundle, computes SHA-256 for each installer, and writesnative-artifacts.json.- From the repo root, use
pnpm --dir apps/protected-registry-desktop verifyorpnpm run verify:protected-registry-desktop.
.github/workflows/protected-registry-desktop.ymlnow gates this package:- Ubuntu contract job runs
pnpm run verify:protected-registry-desktop. - Windows native job installs Rust stable, runs
pnpm run verify:protected-registry-desktop:native, executescargo checkandcargo testfor the Tauri host, verifies production hardening packages, runspnpm --dir apps/protected-registry-desktop tauri:build, and smoke tests the built executable. - The native job then runs
verify:native-artifactsand uploadsnative-artifacts.jsonwith MSI/NSIS installers as theprotected-registry-desktop-windowsartifact.
- Ubuntu contract job runs
- The static UI shell now has a Tauri bridge:
- the first screen is login-only until the host reports an authenticated session
- the login screen exposes only
Login with FractalOps; OIDC issuer, client, redirect, and callback details are app-owned configuration, not user-facing fields - FractalOps/OIDC endpoints come from the repository root
.envor build env:FRACTALOPS_BASE_URLfor FractalOps API calls,PROTECTED_REGISTRY_OIDC_ISSUER_URL,PROTECTED_REGISTRY_OIDC_CLIENT_ID, andPROTECTED_REGISTRY_OIDC_REDIRECT_URIfor login; the build also acceptsFRACTALOPS_PORTAL_PUBLIC_URLas a legacy portal URL fallback - the native host opens the FractalOps authorization URL in the browser and uses a local loopback callback listener to save the completed session back into the desktop app without manual callback pasting
- package actions remain hidden and disabled before authentication
session_storage_labelreturns the OS secure-store label to the UIprotected_registry_sessionreturns a token-free public session status to the UIcomplete_protected_registry_loginstores the completed session through the desktop CLI/session vault boundaryqueue_protected_registry_commandaccepts package command intent without token fields- UI offline fallback still works outside Tauri for browser inspection/tests
- The Rust host now maps queued package intents into an explicit
protected-registryCLI command spec with argv andsession-vault-onlytoken handling, so the privileged native boundary is defined without exposing access tokens in the UI. - The Rust host also exposes
run_protected_registry_command, which validates package/build/delivery intents and executesprotected-registrywithout a shell. It removes portal access-token and delivery-key environment variables before launch so commands must use the session vault or explicit secure provider flow. - Rust host unit tests cover native command argv mapping, delivery required fields, unsupported command rejection, blocked secret environment keys, and a Windows mock CLI execution that proves secret env values are stripped before process launch.
- Native execution resolves the CLI through
PROTECTED_REGISTRY_CLI_PATHfirst and falls back toprotected-registryonPATH; the doctor reports this asdesktop-cli-runtime-pathfor built-app verification.
Still pending:
- Windows native CI packaging evidence still needs to be collected and attached to the branch once CI runs.
Current local native evidence:
cargo check --manifest-path apps/protected-registry-desktop/src-tauri/Cargo.tomlpasses on Windows with Cargo1.96.0.cargo test --manifest-path apps/protected-registry-desktop/src-tauri/Cargo.tomlpasses 5 Rust host tests.pnpm --dir apps/protected-registry-desktop verify:nativepasses 47 desktop tests plus strict native readiness.pnpm --dir apps/protected-registry-desktop tauri:buildproduced both installers:- MSI:
src-tauri/target/release/bundle/msi/Protected Registry_0.1.0_x64_en-US.msi(sha256=b96943dd6a8c1015be1bc8056ea35fdbd7e76929eee51d0cf96c6a713d1c9bc9) - NSIS:
src-tauri/target/release/bundle/nsis/Protected Registry_0.1.0_x64-setup.exe(sha256=1e0784168b13229b5f3709bd95fa9c0574d38f294156a872d22a0bdc354905b2)
- MSI:
pnpm --dir apps/protected-registry-desktop verify:native-artifactsconfirms both installer artifacts and writes the localnative-artifacts.jsonreport.- The built release executable starts and remains running for a 5-second local smoke check, then closes cleanly.
CI evidence attachment checklist:
- GitHub Actions workflow:
.github/workflows/protected-registry-desktop.yml - Required job:
Native package verifyonwindows-latest - Required green steps:
Verify native readinessVerify production hardening packagesCargo check Tauri hostCargo test native command hostBuild Windows desktop packageSmoke test built executableVerify native package artifactsactions/upload-artifact@v4
- Required uploaded artifact:
protected-registry-desktop-windows - Required artifact contents:
apps/protected-registry-desktop/native-artifacts.json- MSI installer under
apps/protected-registry-desktop/src-tauri/target/release/bundle/**/*.msi - NSIS installer under
apps/protected-registry-desktop/src-tauri/target/release/bundle/**/*.exe
- Attach the Actions run URL and the
native-artifacts.jsonSHA-256 entries to the branch or PR once CI completes.
Desktop App CLI Meaning
Section titled “Desktop App CLI Meaning”The CLI requested here is a desktop-app CLI surface, not a CLI-only product.
Recommended shape:
Protected Registry Desktop App -> graphical session and project/package view -> embedded command runner -> local credential/session vault -> npm/pnpm/yarn wrapper -> protected build and delivery commandsThe command surface can still expose commands, but it belongs to the desktop application:
protected-registry loginprotected-registry install @scope/packageprotected-registry publishprotected-registry protect-buildprotected-registry deliver --customer <customer_slug>The desktop app owns the login session, token cache, prompts, project selection, customer selection, and local audit view. The command surface is a controlled automation layer inside that app.
Install Flow
Section titled “Install Flow”developer -> desktop app opens OIDC login -> user selects project/package/action -> app requests npm token from FractalOps -> FractalOps checks SCIM entitlement -> FractalOps returns short-lived .npmrc -> app writes temp .npmrc -> app runs npm/pnpm/yarn install with --userconfig -> app deletes temp .npmrc -> app records local and remote audit eventNo long-lived npm token should be stored. The desktop app may cache the OIDC session or refresh token only through an OS-backed secure store.
Web Registry Flow
Section titled “Web Registry Flow”browser -> Nexus public URL -> Pomerium OIDC -> Keycloak identity and SCIM-projected groups -> nexus-pomerium plugin -> allow package read/publish by groups and package scopeThe browser path is for registry UI and human inspection. Automated install and publish should prefer the desktop app broker path.
Protected Build Flow
Section titled “Protected Build Flow”The package hardening pipeline must not be treated as simple minification.
source package -> compile -> tree-shake -> minify -> obfuscate -> remove public source maps -> create private debug/source-map artifact -> create SBOM and provenance -> create integrity manifest -> publish protected package to Nexus -> record evidence in FractalOpsuglified means code is harder to inspect. It does not mean encrypted. Customer
delivery requires a separate encryption step.
Customer Delivery Flow
Section titled “Customer Delivery Flow”protected package artifact -> encrypt delivery bundle -> store bundle in project delivery area -> store key reference in OpenBao or KMS -> record manifest and evidence -> grant customer-specific accessNever store the decryption key beside the encrypted artifact.
Delivery artifact metadata:
{ "tenant_id": "default", "project_slug": "example-project", "customer_slug": "acme", "package_name": "@customer/example-core", "version": "1.0.0", "protection": { "obfuscated": true, "encrypted": true, "source_maps": "withheld" }, "integrity": { "sha256": "<artifact-sha256>" }, "key_ref": "openbao://projects/example-project/customers/acme/package-delivery"}Application Modules
Section titled “Application Modules”Recommended desktop app modules:
apps/protected-registry-desktop/ src/auth/ oidc-session secure-store src/fractalops/ portal-api-client entitlement-client credential-broker-client src/package-manager/ npm-runner pnpm-runner yarn-runner temp-npmrc src/protection/ build-pipeline obfuscation-policy encryption-client manifest-writer src/delivery/ customer-bundle project-artifact-store evidence-writer src/ui/ project-picker package-picker token-grant-view delivery-view src/cli/ command-routerBackend additions:
POST /v1/packages/registry/tokens/npmPOST /v1/packages/protection/buildsPOST /v1/packages/delivery/bundlesGET /v1/packages/delivery/bundles/{bundle_id}GET /v1/packages/delivery/viewPOST /v1/packages/delivery/grantsThese can reuse the existing PortalCredentialBrokerService and
RegistryTokenService, but they need stricter package/project/action claims.
MVP Scope
Section titled “MVP Scope”MVP should close one install loop first:
- Desktop app OIDC login.
- Project and package selection.
- SCIM entitlement check before token mint.
- Short-lived
.npmrcreturned from FractalOps. - Desktop app runs package manager with temp
.npmrc. - Temp credential cleanup.
- Audit event recorded.
Second slice:
- Add publish token with package/action claims.
- Hardened build pipeline.
- Protected package publish.
- Customer encrypted delivery bundle.
- Portal evidence and delivery view.
Both MVP slices now have backend, desktop, and native-host contract coverage in this branch. Local Windows native verification has produced MSI and NSIS installers; CI packaging evidence remains pending until the Windows job runs.
Closed Design Decisions
Section titled “Closed Design Decisions”These defaults are now encoded in the branch implementation and contract tests.
- Desktop stack: Tauri or Electron? Decision: Tauri.
- Package managers: npm only or npm/pnpm/yarn? Decision: support npm, pnpm, and yarn through the same short-lived token and per-command npmrc flow.
- Token lifetime: how short? Decision: 15 minutes for install, 5 minutes for publish.
- Does customer delivery include runtime decryption? Decision: no for MVP. Deliver encrypted bundle and key grant separately.
- Are source maps escrowed? Decision: yes. Keep private debug artifacts out of Nexus tarballs.
- Is publish allowed from developer machine? Decision: only for maintainers; CI remains normal publish path.
- Does Nexus enforce package-level claims? Decision: yes. FractalOps token mint and Nexus plugin both check.