Skip to content

cosentinode/rolegit

Repository files navigation

RoleGit

RoleGit adds cryptographic file permissions to Git repositories. In the target local-first design, GitHub collaborators can clone the same repository while each protected version is decryptable on devices authorized by a customer-controlled policy when that version is sealed. Removing a recipient prevents access to future versions only after sealers receive fresh policy; it does not revoke older ciphertext and wrapped keys retained in Git history. The current prototype instead uses a customer-run authorization service that can unwrap data keys and is therefore decrypt-capable.

This is an early TypeScript 7 prototype. It includes encrypted vault objects, GitHub device-flow interfaces, server-side authorization, and fixed-duration sessions.

Warning

The centralized authorization service is an experimental, self-hosted-only prototype for validating the current security model. It is not a public RoleGit SaaS offering, is not production-ready, and will be superseded by local-first Community mode. See docs/security.md and do not use this baseline with real secrets.

The target Community, Team, and Enterprise boundaries are defined in ADR 0001. Community is local-first by default; its Git/customer synchronization path remains trusted for signed-state freshness and can delay revocation under a stale or split view. Team distributes customer-signed metadata without receiving decryptable key material, although its coordinator has the analogous freshness boundary until the transparency protocol is specified. Enterprise key authority stays in customer-controlled devices, KMS, or self-hosted infrastructure. Across modes, revocation cannot recall plaintext or DEKs already released. Recipient-mode removal from retained encrypted history requires re-encryption and history rotation, and no mode can erase copies someone already made. The recipient policy does not yet authorize sealing identities or require sealer signatures. Until a versioned sealer-authentication protocol exists, Community, Team, and Enterprise content-blind recipients trust repository write controls, review, and Git provenance against forged replacement content; a writer can forge a new decryptable object without learning the displaced plaintext. Branch, protocol, and MIT repository governance are defined in ADR 0002.

Requirements

npm install
npm run build
npm link

Repository Setup

rolegit init --server http://127.0.0.1:8787
rolegit protect .env

.enclist is tracked and maps protected plaintext paths to opaque encrypted objects:

{
  "version": 1,
  "vaultId": "generated-uuid",
  "authServer": "http://127.0.0.1:8787",
  "files": {
    ".env": {
      "object": ".rolegit/vault/opaque-digest.json"
    }
  }
}

The prototype also reads authServer from this tracked file. A repository writer or Git split view can replace it, and the CLI checks only that a non-loopback endpoint uses HTTPS; it does not pin the endpoint to the intended customer service identity. On a fresh checkout, or a later login after the previous session is no longer active, the selected endpoint controls the authentication exchange and the key-generation and unwrap requests used by later seal and unlock operations. GitHub login authenticates the user to that selected service, not the service to the user.

An active session or materialization lease prevents a transparent server replacement, and lock remembers the machine-local server association rather than trusting a later checkout change. Those controls do not protect initial or subsequent bootstrap. Before every prototype login, an administrator must distribute the expected canonical endpoint through a channel outside Git, and the user must verify the exact .enclist.authServer value and stop if it changed unexpectedly. Standard HTTPS validation alone does not establish that this is the customer's intended service. Durable service-identity pinning or removal of this key path is tracked in issue #55.

rolegit protect refuses already tracked files and adds the plaintext path to .gitignore. For this experimental self-hosted prototype, copy the generated vault ID and protected paths into an authorization-service policy based on server-policy.example.json. The policy of the service actually contacted authorizes its requests. Changing only tracked vault or path data cannot alter the expected service's policy, but replacing authServer can bypass that service for future seals: a replacement service can return a DEK and wrapped key it controls and later decrypt or forge that resulting encrypted version if it obtains the committed object. Redirection does not by itself reveal an older object sealed through the expected service because the replacement service cannot unwrap its DEK, although it can deny access by receiving the unwrap request instead.

Local End-to-End Development

Generate a development key-encryption key:

export ROLEGIT_KEK="$(node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("base64url"))')"
export ROLEGIT_GITHUB_APP_KEY="$HOME/.config/rolegit/rolegit-auth.private-key.pem"
export ROLEGIT_DEV_AUTH=1
rolegit serve --policy ./server-policy.json

In another terminal, use a numeric user ID explicitly configured in the server policy:

rolegit login --development-user 12345678
rolegit seal
git add .enclist .gitignore .rolegit/vault
git commit -m "Add encrypted environment"

After cloning on another machine:

rolegit login --development-user 12345678
rolegit unlock
# .env now exists locally (mode 0600 on POSIX systems)
rolegit status
rolegit lock

Development authentication is deliberately unsafe and only binds to the loopback service.

Development Policy

develop is the integration branch and the base for feature and maintenance pull requests. main contains stable release history and is the base for reviewed release-promotion pull requests from tested develop history, not direct development. All changes must land through a pull request. The repository CI workflows delivered by PR #52 run typecheck, build, test, package dry-run, CLI smoke, and pull-request title checks for pull requests to both develop and main; branch protection requires those check contexts on both branches. That is not yet trusted, unspoofable enforcement because the required contexts are produced by the generic GitHub Actions app and a pull-request-controlled workflow can duplicate them. Issue #2 remains open for secure enforcement. Stable publication also remains disabled until package ownership and trusted publishing are configured.

GitHub Login

Install the RoleGit GitHub App, leave ROLEGIT_DEV_AUTH unset, and run:

rolegit login

RoleGit prints GitHub's verification URL and device code. The service resolves the authenticated user's immutable numeric ID and checks configured team membership before every key operation. Sessions default to 60 minutes and cannot be silently refreshed.

The official app's public identity is built into RoleGit. Self-hosted services can override its public Device Flow client ID with githubClientId in their server policy. Client secrets and app private keys are never distributed with the CLI.

The authorization service can use ROLEGIT_GITHUB_APP_KEY to obtain repository-scoped installation tokens. This lets it verify organization-team membership independently instead of trusting a client-supplied result. The private key belongs only on the authorization service.

Daily Workflow

rolegit login
rolegit unlock

# Edit and run the project normally.
$EDITOR .env

rolegit seal .env
git add .rolegit/vault
git commit -m "Update encrypted environment"
git push

rolegit lock

An expiry watcher removes unchanged materialized files after the session that unlocked them expires. rolegit lock performs the same cleanup immediately, attempts to invalidate the server session, and always clears the local session token when its repository/server association is available, even when no files are unlocked. It reports remote logout failures; the remote token then remains valid only until its fixed expiry. unlock reserves cleanup ownership before creating each plaintext path, and a failed partial unlock retains ownership for any rollback failure. RoleGit preserves any materialized path that was modified or replaced after the latest successful unlock or seal rather than risking data loss. On POSIX systems, lease reservations fsync both their file and containing state directory before plaintext creation, and plaintext creation/removal fsyncs its containing directory. Explicit lock reports changed paths and relinquishes its cleanup lease; failed automatic expiry cleanup durably retires each successfully removed path before continuing and retains only failed paths, so retries cannot act on newly recreated files. A new identity cannot log in until the remaining paths are resolved or explicitly locked. Local sessions are repository-scoped even when repositories use the same authorization server, so lock invalidates only the current repository's token. A new login cannot replace an active local session or proceed while a live materialization lease remains. Login safely cleans unchanged files from an expired lease first and remains blocked if that cleanup reports modified paths.

RoleGit stores a checkout UUID in Git's private metadata so local cleanup ownership survives moving or renaming the checkout without being committed. The UUID is bound to one machine-local checkout directory by persisted device/inode identity, so aliases to that directory retain access while a distinct copy fails closed before accessing sessions, leases, or plaintext. Missing markers and alternate active Git directories recover the registry identity for that filesystem instance; conflicting registered identities fail with cleanup instructions instead of opening a new state namespace. Leases and their expiry watchers also carry a unique generation so an older watcher cannot clean or alter failure state for a replacement lease. Successful cleanup atomically replaces the lease with a completed-generation tombstone, preventing a late watcher from recreating a failure after lock; repeated unlock attempts retain the active generation so rollback remains owned by its existing watcher. Expiry watchers use the checkout UUID and machine-local registry rather than a fixed path; a same-parent rename is rediscovered automatically, while an ambiguous or unlocatable move retains the lease and a generation-scoped persistent warning for the next command. Remove any copied materialized plaintext before assigning the copy a fresh identity. ROLEGIT_HOME, when set, must be absolute and outside the current worktree so commands cannot select repository-stageable control state or change namespaces by working directory. Repository mutations, including init and protect, and session operations use machine-local lock files. A lock left by a terminated process is never removed automatically: commands fail with its path so the user can verify no RoleGit process is active before removing it.

Current Scope

  • Exact protected paths; Git-ignore-style patterns are planned.
  • Protected paths reject Windows device names, alternate-data-stream syntax, trailing dots/spaces, .gitignore, active Git directories/index/object/shallow/config metadata, RoleGit metadata namespaces, multiply-linked files, and portable policy/Git/worktree aliases. Unicode collision keys use NFC and locale-independent, one-code-point uppercase/lowercase folding; generated ignore rules expand the same aliases. Multi-character case mappings and paths requiring more than 1,024 ignore alternatives are rejected. Node does not expose the active Windows filesystem case table, so this is a conservative Unicode approximation rather than a guarantee of every OS-version-specific equivalence. Filenames such as __proto__ and constructor remain valid user paths.
  • Server state and sessions are in memory and disappear on restart.
  • The key-encryption key comes from ROLEGIT_KEK; production KMS integration is not implemented.
  • On POSIX systems, session tokens and materialized files are created with mode 0600. Node's numeric modes do not configure Windows ACLs; use a private Windows profile and appropriate ACLs. OS keychain storage is planned.
  • The authorization service binds to loopback; production TLS and deployment are not implemented.
  • Current v1 encrypted-object, .enclist, and server-policy readers reject unsupported versions and validate recognized fields but ignore unknown object fields. This artifact-specific behavior is a documented prototype exception, not a guarantee for every JSON response or a stable extensibility guarantee; producers must not encode security semantics in unknown fields. See ADR 0002.
  • There is no encrypted merge-conflict workflow yet.

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages