@idp.global/devidp
@idp.global/devidp runs an exact local idp.global facade with fixed test identities and direct TLS. It consumes the canonical schema-v3 contract from @idp.global/interfaces/devidp, retains small stable secrets and a namespace CA in the Linux kernel keyring, starts the immutable idp.global app image (with an optional pinned or locally built override) against a namespace-isolated @lossless.org/nosqldb engine, and returns the relying-party credentials and TLS material needed by a local application.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Runtime Requirements
- Linux x64
- Node.js 24.18 or Node.js 25
- a locally reachable Docker Engine and Docker CLI
- pnpm 11.18 or newer for development
- a usable persistent user kernel keyring
- a resolvable
@lossless.org/nosqldbRust engine binary: the bundled platform executable, an explicitNOSQLDB_RUST_BINARY, or a local engine build
The package uses only SmartSecretKernelStore from @push.rocks/smartsecret. It never falls back to Secret Service, environment variables, or filesystem persistence. Unsupported or unavailable kernel-keyring environments fail closed.
Installation
pnpm add @idp.global/devidp
Usage
import { DevIdp } from '@idp.global/devidp';
const devIdp = new DevIdp({
namespace: 'freelance-local',
port: 8443,
rpClient: {
name: 'freelance.club local',
appUrl: 'https://localhost:3000/',
redirectUris: [
'https://localhost:3000/auth/callback',
'https://superadmin.localhost:3000/auth/callback',
],
postLogoutRedirectUris: [
'https://localhost:3000/',
'https://superadmin.localhost:3000/',
],
tlsHostnames: ['localhost', 'superadmin.localhost'],
},
});
const runtime = await devIdp.start();
console.log(runtime.origins.issuer);
console.log(runtime.client.clientId);
console.log(runtime.personas.testuser.email);
console.log(runtime.personas.testmoderator.email);
console.log(runtime.personas.testsuperadmin.email);
// Give runtime.caCertificatePem, runtime.rpTls, and runtime.client to the
// relying party, then retain devIdp for shutdown.
await devIdp.stop();
start() returns only after the app-owned result matches the exact manifest and all four direct HTTPS surfaces are ready: idp.localhost, login.idp.localhost, app.idp.localhost, and superadmin.idp.localhost. The app port is published only on 127.0.0.1. The database engine binds only to the gateway address of this lifecycle's own internal Docker network and uses ephemeral memory storage.
The returned runtime contains the stable client id and secret, fresh relying-party TLS leaf, public local CA, and the email, password, subject, and exact authority for all three personas. stop() removes only the containers, networks, and temporary files captured by that lifecycle. Stable identity material remains in the kernel keyring for deterministic restart behavior.
withRuntimeMaterial() normalizes the complete manifest through the public schema-v3 owner before it touches durable or cryptographic state. Persona names and authority are therefore fixed:
testuser: ordinary user, no global capabilitiestestmoderator: non-admin with exactlyglobal.alert.manage,global.alert.read,global.user.manage, andglobal.user.readtestsuperadmin: global administrator with no direct capability assignments
Protected mutations continue to require the normal strong-MFA rules; the local profile does not create an MFA bypass.
For lower-level material inspection, deletion, explicit legacy migration, or callback-scoped access, use DevIdpFoundation. Application development should normally use the owning DevIdp lifecycle above.
Storage Contract
Every namespace is guarded by one cross-process namespace mutex and consists of one receipt plus nine deterministic payload accounts. Payloads bind their namespace digest, generation, purpose, kind, and value. The receipt binds the exact payload inventory plus every byte length and SHA-256 digest.
Creation writes and read-verifies every bounded payload before it writes the receipt. The receipt is the commit point and is written and read-verified last. Reads distinguish:
absent: receipt and all fixed payloads are absentincomplete: the receipt is absent but at least one fixed payload remainscommitted: the receipt and every bound payload validate exactly
A committed mismatch is corruption and fails closed. Incomplete uncommitted state can be cleaned and recommitted under the namespace mutex. Deletion invalidates the receipt first, removes every fixed payload, and absence-verifies the entire inventory.
The stable record contains independent 32-byte values for the confidential client, audit protector, TOTP encryption, OIDC transaction protection, and all three persona credentials. It also contains one namespace-bound P-256 CA and private key. Each kernel entry is at most 1,024 bytes.
Direct Local TLS
The stable CA is never silently rotated. Each withRuntimeMaterial() call creates independent fresh 30-day P-256 leaves:
- the IdP leaf contains exactly
idp.localhost,login.idp.localhost,app.idp.localhost, andsuperadmin.idp.localhost; - the relying-party leaf contains exactly the normalized
rpClient.tlsHostnamesinventory.
Issuance fails when the stable CA cannot contain the full leaf-validity window. The package never changes operating-system or browser trust stores; callers may explicitly retrieve the public CA certificate with readCaCertificatePem() and own any trust bootstrap themselves.
Legacy Keyutils Migration
migrateLegacyKeyutilsV1(namespace) is an explicit operator action. Stop the legacy writer first. The method reads only the narrow v1 keyutils source owned by SmartSecretKernelStore, strictly validates the entire legacy envelope, commits and reloads the v2 destination, and only then consumes the store-bound legacy receipt.
A keyutilsSourceAbsent result does not prove that Secret Service contains no old value. The package never probes or adopts Secret Service. Equivalent committed v2 and legacy values reconcile; conflicting or changed sources fail closed.
Resource and Secret Lifetime
Every operation attempts both kernel-store close and namespace-lease release, including failure paths. Simultaneous operation and cleanup failures are aggregated rather than swallowed. Callback-owned secret byte arrays are wiped afterwards on a best-effort basis, but JavaScript strings, garbage collectors, crypto libraries, and operating-system buffers may retain copies.
DevIdp starts one exact digest-bound app image and one in-process database engine. The app container is attached to an internal data network and to one dedicated bridge used for the loopback-only published TLS port. No Caddy or other local reverse proxy is started.
The datastore is the published @lossless.org/nosqldb engine — the exact version idp.global/app itself pins — started through its supported NoSqlDbServer API in the facade process. The package ships no container image and no standalone server entrypoint contract, so an engine container would mean depending on an undocumented interface; the supported embedded server is used instead. It binds to the IPv4 gateway address of this lifecycle's own --internal network and the app container resolves the namespace-derived database alias to exactly that address. The listener is never published on a host port and is never bound to a wildcard address, so it is not exposed on any external interface; processes on the host that can route to that Docker bridge address can still reach it, and access is SCRAM-authenticated with a per-lifecycle random password. Two namespaces get two different gateway addresses and never collide on one host port. Access stays SCRAM-authenticated with a per-lifecycle random password, and the connection URL keeps the isolated-target shape the app validates: mongodb://<derived user>:<random password>@<derived alias>:27017/<derived database>?authSource=<derived database>&directConnection=true. Storage is ephemeral memory storage, matching the previous disposable data directories; durable identity lives in the kernel keyring. Transactions work because the engine supports them natively, which the app requires from 14.x onwards.
One condition conflicts with this design: the database port 27017 is pinned by the app's isolated-target URL contract, so a host-wide wildcard listener already bound to 0.0.0.0:27017 (or [::]:27017) takes the gateway address with it and the lifecycle fails to start. Stop or rebind that listener; a listener bound to 127.0.0.1:27017 does not conflict.
The public constants expose the immutable artifacts used by every lifecycle:
devIdpAppImage:code.foss.global/idp.global/app:14.0.1@sha256:0634d1f1aed28b78425652cc03edd9a16143d3184efa24be10c05347c3237103
The app image serves the organization-capable login profile: its discovery
document advertises the organizations and roles scopes together with the
canonical /oauth/introspect and /oauth/userinfo endpoints, and the runtime
seeds one organization owned by the testuser persona. testmoderator and
testsuperadmin hold no membership, so a global administrator without an
organization membership is denied organization authority exactly as in
production. Organization identity is observable only through the ordinary
organizations claim; the result contract carries no organization fields.
The optional appImage option replaces the app image for one lifecycle:
{ reference: '<name>[:<tag>]@sha256:<64 lowercase hexadecimal characters>' }qualifies a different pinned published image. It is pulled before use.{ reference: '<name>:<tag>', localBuild: true }qualifies a locally built candidate image. It is never pulled and must already exist in the local image store, so an unpublished candidate can be qualified without a registry.
localBuild must be true for a local reference and absent or false for a
pinned published reference. Any other shape, an unpinned published reference, a
digest reference declared as a local build, or an unknown field is refused
before the lifecycle starts. The database engine is not an image and is not overridable.
SERVEZONE_PLATFORM_AUTHORIZATION=test keeps the app's platform client in
debug mode, so the facade needs no Cloudly origin and no mail credentials even
though the app now requires an authenticated Socket 8 platform session outside
debug mode.
The lifecycle passes secrets through distinct protected disposable files or mode-0600 Docker environment files, never through Docker command arguments. The app writes its result inside the container; the lifecycle copies and validates it before returning. Startup failure and shutdown both remove owned resources in dependency order and aggregate cleanup failures instead of hiding them.
If cleanup cannot remove an owned resource, its immutable id or unresolved exact-name/lifecycle recovery descriptor remains captured and the lifecycle remains in stopping. A later stop() retries recovery and those exact remaining ids; a new start() is refused until cleanup completes.
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.