DEV Community

Cover image for πŸš€ GitGuard – Secure Just-In-Time Repository Access with Biometrics & Permit.io
NIkhil Sahni
NIkhil Sahni

Posted on

πŸš€ GitGuard – Secure Just-In-Time Repository Access with Biometrics & Permit.io

πŸ” GitGuard – Just-in-Time GitHub Access Control

Submission for the Permit.io Authorization Challenge: Permissions Redefined

What I Built

I built GitGuard - a full-stack, production-grade access control and auditing system for secure, temporary, and role-based GitHub access management that leverages Permit.io for dynamic authorization.

In traditional GitHub environments, access control follows a static model: either you have access to a repository, or you don't. This creates security risks as teams often grant excessive permissions to ensure work isn't blocked. GitGuard solves this by implementing Just-in-Time access - granting temporary, scoped permissions only when needed, verified through biometric authorization.

Think of GitGuard as a "Just-in-Time IAM layer" tailored for GitHub. Perfect for fast-moving teams that need security without sacrificing agility.

Demo Screenshots

πŸ” Login Screen

Login Screen

πŸ“ Register Screen

Register Screen

🏠 Home Page

Home Page

πŸ“ Repository Screen

Repository Screen

πŸ—‚οΈ Access Request Manager

Access Request Manager

βœ… Approval/Reject Filter

Approval/Reject

βœ”οΈ Approve Request Flow

Approve Request

πŸ“₯ Access Request Form

Access Request Form

🧬 Biometric Approval (Simulated)

Biometrics cannot be captured in screenshots; simulated via mobile preview.

Biometric Approval

πŸ”” Push Notification for Approvals

Push Notification

πŸ“‚ Repository Details

Repository Details

πŸ”” Push Notifications List

Push Notifications List

🏒 Organisation List and Create

Organisation List

πŸ“œ Audit Logs

(Local preview only)
Audit logs 1
Audit logs 2

Key Features

Feature Description
πŸ” Biometric Authentication Approve access requests using fingerprint/face ID
⏱️ Just-in-Time Access Time-bound repository access with automatic expiration
πŸ‘₯ Role-Based Access Multiple repository roles with different permission sets
πŸ“Š Audit Logging Comprehensive activity tracking for compliance
πŸ”” Push Notifications Instant alerts for access requests and approvals
πŸ”„ Multi-Approver Flow Quorum requirements for sensitive repositories
🚨 Emergency Access Expedited access for critical situations
πŸŒ™ Auto-Expiration Automatic revocation after defined timeframes
🏦 Organization Grouping Manage access across multiple organizations

Role-Based Capabilities

Feature Viewer Contributor Admin
View Repository βœ… βœ… βœ…
Clone Repository βœ… βœ… βœ…
Push Changes ❌ βœ… βœ…
Approve Access ❌ ❌ βœ…
Repository Settings ❌ ❌ βœ…
Delete Repository ❌ ❌ βœ…
Create Repository ❌ ❌ βœ…
View Audit Logs ❌ ❌ βœ…

Project Repositories

Component Link
🧠 Backend GitGuard Backend
πŸ“± Mobile App GitGuard Mobile

Permissions Redefined with Permit.io

GitGuard implements a true Permissions Redefined model using Permit.io's policy-as-code approach, completely separating the business logic from the authorization layer.

Core Authorization Flow

  1. User initiates an access request for a repository
  2. Admin receives notification and authenticates with biometrics
  3. Backend verifies biometric token and processes approval
  4. Permit.io is used to check, assign, and enforce permissions
  5. Time-bound role is assigned to the user
  6. Access is automatically revoked after expiration
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Mobile │───▢│ GitGuard API │───▢│ permitUtils│───▢│ Permit.io β”‚ β”‚ App │◀───│ │◀───│ middleware│◀───│ Cloud PDP β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–² β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Policies defined in Permit.io dashboard 
Enter fullscreen mode Exit fullscreen mode

Implementation

GitGuard implements authorization with a modular, clean approach through a dedicated permitUtils.ts layer:

// Backend initialization (src/index.ts) export const permit = new Permit({ token: process.env.PERMIT_API_KEY || "", pdp: process.env.PERMIT_PDP_URL || "http://localhost:7766", }); 
Enter fullscreen mode Exit fullscreen mode
// Permission check implementation (src/utils/permitUtils.ts) export const checkPermission = async ( userId: string, action: string, resource: string, resourceInstance?: string ) => { let resourceObj: string | { type: string; id: string } = resource; // If resource instance is provided, create resource object if (resourceInstance) { resourceObj = { type: resource, id: resourceInstance, }; } // Try to check permission with Permit.io first try { const permitted = await permit.check(userId, action, resourceObj); if (permitted) return true; } catch (permitError) { console.warn( `Permit check failed for user ${userId} on ${resource}:${resourceInstance} - ${permitError}` ); // Continue to fallback check } // If Permit.io check fails or returns false, fall back to database check if (resource === "repository" && resourceInstance) { const { prisma } = await import("../index"); // Check if user is the repository owner const repo = await prisma.repository.findUnique({ where: { id: resourceInstance }, select: { ownerId: true }, }); if (repo && repo.ownerId === userId) { return true; // Repository owners have all permissions } // Check role assignments const roleAssignment = await prisma.roleAssignment.findFirst({ where: { userId, repositoryId: resourceInstance, }, include: { role: { include: { permissions: true, }, }, }, }); if (roleAssignment) { // Check if the assigned role has the required permission const hasPermission = roleAssignment.role.permissions.some( (permission) => permission.action === action || permission.action === "admin" ); if (hasPermission) { return true; } } } return false; }; 
Enter fullscreen mode Exit fullscreen mode
// Role assignment (src/utils/permitUtils.ts) export const assignRoleInPermit = async ( userId: string, roleKey: string, resourceType: string, resourceInstanceKey: string ) => { try { // First ensure the user exists in Permit.io try { await syncUserWithPermit(userId); } catch (userError) { console.warn(`Could not sync user with Permit.io: ${userError}`); // Continue anyway - we'll try to assign the role } await permit.api.roleAssignments.assign({ user: userId, role: roleKey, tenant: "default", resource_instance: `${resourceType}:${resourceInstanceKey}`, }); console.log( `Successfully assigned role ${roleKey} to user ${userId} for ${resourceType}:${resourceInstanceKey}` ); return true; } catch (error: any) { // If it's a 409 conflict (role already assigned), treat as success if (error.response && error.response.status === 409) { console.log( `Role ${roleKey} already assigned to user ${userId}, skipping` ); return true; } console.error("Failed to assign role in Permit.io:", error); // Don't throw the error, just log it and continue - this makes the app more resilient // We'll fall back to database checks for permissions return false; } }; 
Enter fullscreen mode Exit fullscreen mode
// Usage in API endpoints (src/routes/repository.ts) router.get("/:id", authenticateJWT, async (req, res, next) => { try { const { id } = req.params; const userId = req.user.id; // Check permission with Permit.io const hasViewPermission = await checkPermission( userId, "view", "repository", id ); if (!hasViewPermission) { throw new ApiError( 403, "You don't have permission to view this repository" ); } // Proceed with repository retrieval... } catch (error) { next(error); } }); 
Enter fullscreen mode Exit fullscreen mode

Dashboard Configuration

For GitGuard to work correctly, you must configure the following in the Permit.io dashboard:

  1. Define Resources:
  • Create repository resource type with actions:
    • view: View repository contents
    • clone: Clone repository
    • push: Push changes to repository
    • admin: Administer repository settings
    • delete: Delete repository
    • create: Create new repository
  1. Define Roles:
  • viewer: Can view and clone repositories
  • contributor: Can view, clone, and push to repositories
  • admin: Has full access to all repository actions
  1. Configure User-to-Role assignments in the Roles tab

  2. Set up Resource Relations for ownership model:

    • Relation: owner between user and repository

Setup Guide

Step 1: Clone the repository

git clone https://github.com/nikhilsahni7/GitGuard.git cd GitGuard 
Enter fullscreen mode Exit fullscreen mode

Step 2: Set up Permit.io

  1. Create a free account at Permit.io
  2. Create a new project
  3. Set up:
    • Resource type: repository
    • Actions: view, clone, push, admin, delete, create
    • Roles: viewer, contributor, admin
    • Configure role permissions as described above
  4. Generate an Environment API key from the dashboard

Step 3: Configure environment variables

Create a .env file in the backend directory:

# Permit.io PERMIT_API_KEY=your_permit_api_key PERMIT_PDP_URL=http://localhost:7766 # Or cloud PDP URL # Database DATABASE_URL=postgresql://user:password@localhost:5432/gitguard # JWT Authentication JWT_SECRET=your_jwt_secret JWT_EXPIRES_IN=7d # GitHub Integration GITHUB_CLIENT_ID=your_github_client_id GITHUB_CLIENT_SECRET=your_github_client_secret # Push Notifications EXPO_ACCESS_TOKEN=your_expo_token 
Enter fullscreen mode Exit fullscreen mode

Step 4: Install dependencies and run

# Backend setup cd backend bun install bun run db:migrate bun run permit:setup bun run dev # Mobile setup (in a separate terminal) cd ../mobile yarn install yarn start 
Enter fullscreen mode Exit fullscreen mode

Step 5: Initialize Permit.io

GitGuard includes a setup script that configures all necessary resources and permissions:

cd backend bun run permit:setup 
Enter fullscreen mode Exit fullscreen mode

This sets up:

  • Repository resource with all actions
  • User resource
  • Standard roles (viewer, contributor, admin)
  • Resource relations for ownership model

Step 6: Verify Permit.io Setup

To verify your Permit.io configuration:

bun run permit:verify 
Enter fullscreen mode Exit fullscreen mode

This will:

  • Check if resources and roles exist
  • Create a test user
  • Assign roles and test permissions
  • Create and test resource relationships

Challenges and Solutions

Challenge 1: Resource Instance Permissions

Initially, I struggled with implementing resource-instance level permissions in Permit.io to grant access to specific repositories rather than all repositories.

Solution: I implemented a robust resource relations system using Permit.io's API:

// Setup owner relation (setup-permit.ts) const relationData = { key: "owner", name: "Owner", subject_resource: "user", }; await permit.api.resourceRelations.create("repository", relationData); // Create relationship tuples for specific repositories await permit.api.relationshipTuples.create({ subject: `user:${userId}`, relation: "owner", object: `repository:${repositoryId}`, tenant: "default", }); 
Enter fullscreen mode Exit fullscreen mode

Challenge 2: Fallback Mechanism

What if Permit.io is temporarily unavailable? GitGuard needed resilience.

Solution: I implemented a dual-check system that falls back to database checks:

export const checkPermission = async ( userId, action, resource, resourceInstance ) => { // Try Permit.io first try { const permitted = await permit.check(userId, action, resourceObj); if (permitted) return true; } catch (permitError) { // Log and continue to fallback } // Fallback to database check // [Database permission check logic omitted for brevity] }; 
Enter fullscreen mode Exit fullscreen mode

Challenge 3: Time-bound Access

Implementing automatic role expiration was critical for the Just-in-Time model.

Solution: Combined Permit.io role assignments with a database TTL mechanism:

// When approving access requests (routes/accessRequest.ts) await prisma.roleAssignment.create({ data: { userId: requestData.userId, repositoryId: requestData.repositoryId, roleId: requestData.roleId, expiresAt: new Date(Date.now() + duration), // Time-bound access approvedBy: adminId, approvedAt: new Date(), }, }); // Also register in Permit.io await assignRoleInPermit( requestData.userId, role.key, "repository", requestData.repositoryId ); // Background job runs to revoke expired access // [Scheduled job implementation omitted for brevity] 
Enter fullscreen mode Exit fullscreen mode

Challenge 4: Biometric Verification Flow

Securing the approval process with biometrics while maintaining a smooth user experience was challenging.

Solution: Implemented a secure token-based verification system:

// Mobile app generates a biometric token (mobile code) const biometricAuth = async () => { const compatible = await LocalAuthentication.hasHardwareAsync(); if (!compatible) { throw new Error("Biometric authentication not available"); } const result = await LocalAuthentication.authenticateAsync({ promptMessage: "Authenticate to approve access request", fallbackLabel: "Use passcode", }); if (result.success) { // Generate token only after successful biometric auth return generateBiometricToken(); } throw new Error("Authentication failed"); }; // Backend verifies token before approving (routes/accessRequest.ts) router.post("/:id/approve", authenticateJWT, async (req, res, next) => { try { const { biometricToken } = req.body; const adminId = req.user.id; // Verify biometric token const validToken = await verifyBiometricToken(adminId, biometricToken); if (!validToken) { throw new ApiError(401, "Invalid biometric verification"); } // Process approval with Permit.io // [Approval logic omitted for brevity] } catch (error) { next(error); } }); 
Enter fullscreen mode Exit fullscreen mode

What I Learned

Building GitGuard with Permit.io provided several key insights:

Technical Benefits

  • Separation of Concerns: Clean separation between business logic and authorization decisions
  • Flexible Policy Management: Ability to update access policies without code changes
  • Resource-Based Model: Modeling GitHub repositories as protected resources with granular permissions

Business Benefits

  • Enhanced Security: Just-in-Time access model vastly reduces the attack surface
  • Centralized Control: Administrators can manage all permissions from one dashboard
  • Audit Compliance: Comprehensive logging of all access decisions
  • Reduced Overhead: Automating approval workflows saves significant administrative time

Developer Experience

  • Cleaner Codebase: Authorization logic centralized in one place rather than scattered throughout
  • Reduced Boilerplate: Fewer permission checks needed in business logic
  • Easier Testing: Simpler mocking of authorization decisions for unit tests

Why Permit.io Works for Just-in-Time Access

Permit.io is particularly well-suited for Just-in-Time access control because:

  1. External Policy Management: Policies can be updated in real-time without deploying code
  2. Resource Instance Granularity: Permissions can be scoped to specific repositories
  3. Relationship Modeling: Owner/member relationships easily modeled in permissions
  4. Flexible Role System: Easy to create and assign temporary roles for specific durations
  5. Audit Trail: Built-in logging for compliance and security reviews

Future Improvements

With more time, I would enhance GitGuard with:

  1. Local PDP: Set up a local Policy Decision Point for improved performance and reliability
  2. Attribute-Based Policies: Extend beyond role-based to include context like time of day, IP range, etc.
  3. Multi-Tenant Support: Enhanced organization isolation for enterprise environments
  4. Custom Policy Editor: Allow admins to create custom policies beyond predefined roles
  5. Integration with CI/CD: Automated access for deployment pipelines with temporary credentials

Built with ❀️ using:
Bun β€’ Prisma β€’ PostgreSQL β€’ Expo β€’ React Native β€’ TypeScript β€’ Permit.io

Top comments (1)

Collapse
 
kush_gautam_8b4bdeabeb6de profile image
Kush Gautam

Good work πŸ‘