Skip to main content

Overview

VulnTrack is built for team collaboration with enterprise-grade multi-tenancy, role-based access control, and comprehensive audit trails. Teams work in isolated workspaces with shared vulnerability tracking, real-time notifications, and threaded discussions.

Team Workspaces

Isolated data environments with team-scoped vulnerability tracking

Role-Based Access

Granular permissions with Admin, Analyst, and Viewer roles

Real-Time Notifications

Instant alerts for assignments, status changes, and comments

Audit Logging

Complete activity tracking for compliance and security monitoring

Team Workspaces

VulnTrack implements strict multi-tenant data isolation:
// From prisma/schema.prisma:10
model Team {
  id        String   @id @default(uuid())
  name      String
  users     User[]
  vulnerabilities Vulnerability[]
  invitations Invitation[]
  auditLogs AuditLog[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
Strict Data Isolation: Users can only access data within their assigned team. Cross-tenant access is blocked at the database query level. Even admins cannot see other teams’ data.

Workspace Queries

// From src/app/actions/vulnerabilities.ts:32
const user = await prisma.user.findUnique({
  where: { id: session.user.id },
  select: { teamId: true, role: true }
})

let whereClause: any = {
  teamId: user.teamId // Scope to team
}

// Strict isolation check
if (!user?.teamId || user.teamId !== vulnerability.teamId) {
  return { success: false, error: "Unauthorized: Cross-tenant access denied" }
}

User Roles

VulnTrack supports three role levels:
Full Administrative AccessPermissions:
  • Create, edit, delete any vulnerability
  • Approve/reject pending submissions
  • Assign vulnerabilities to team members
  • Manage team users and invitations
  • Access audit logs
  • Configure team settings
Use Cases:
  • Security team leads
  • CISO and security managers
  • Compliance officers

Role Schema

// From prisma/schema.prisma:27
model User {
  role          String    @default("VIEWER") // ADMIN, ANALYST, VIEWER
  status        String    @default("ACTIVE") // ACTIVE, INACTIVE, PENDING
  
  teamId        String?
  team          Team?     @relation(fields: [teamId], references: [id])
}

Comments System

Threaded discussions on vulnerabilities:
// From src/app/actions/comments.ts:11
export async function addComment(vulnerabilityId: string, content: string) {
  const session = await getServerSession(authOptions)
  
  // Verify user belongs to same team as vulnerability
  const user = await prisma.user.findUnique({
    where: { id: session.user.id },
    select: { teamId: true }
  })
  
  const vuln = await prisma.vulnerability.findUnique({
    where: { id: vulnerabilityId },
    select: { teamId: true }
  })
  
  if (!user?.teamId || user.teamId !== vuln.teamId) {
    return { success: false, error: "Unauthorized access" }
  }
  
  const comment = await prisma.comment.create({
    data: {
      content,
      vulnerabilityId,
      userId: session.user.id
    },
    include: {
      user: {
        select: { name: true, email: true, image: true }
      }
    }
  })
  
  await logAudit(
    "ADD_COMMENT",
    "Vulnerability",
    vulnerabilityId,
    `Comment added by ${session.user.email}`
  )
  
  return { success: true, data: comment }
}

Comment Schema

// From prisma/schema.prisma:93
model Comment {
  id              String        @id @default(uuid())
  content         String
  userId          String
  user            User          @relation(fields: [userId], references: [id])
  vulnerabilityId String
  vulnerability   Vulnerability @relation(fields: [vulnerabilityId])
  createdAt       DateTime      @default(now())
}
Comments support Markdown formatting for rich text discussions with code blocks, lists, and formatting.

Notification System

Real-time alerts for critical events:
// From src/app/actions/notifications.ts:70
export async function createNotification(data: {
  userId: string
  type: string
  title: string
  message: string
  link?: string
}) {
  const notification = await prisma.notification.create({
    data: {
      userId: data.userId,
      type: data.type,
      title: data.title,
      message: data.message,
      link: data.link
    }
  })
  return { success: true, data: notification }
}

Notification Types

VULNERABILITY_ASSIGNED

Triggered when a vulnerability is assigned to a user. Includes link to vulnerability details.

STATUS_CHANGED

Notifies stakeholders when vulnerability status changes (OPEN → IN_PROGRESS → RESOLVED).

COMMENT_ADDED

Alerts when new comments are added to vulnerabilities you’re involved with.

APPROVAL_REQUIRED

Notifies admins when analyst submissions require approval.

Email Notifications

// From src/app/actions/vulnerabilities.ts:456
// Assignment notification with email
const assigneeUser = await prisma.user.findUnique({
  where: { id: assigneeId },
  select: { email: true }
})

if (assigneeUser?.email) {
  const { sendEmail } = await import("@/lib/email")
  const { getAssignmentEmail } = await import("@/lib/email-templates")
  
  await sendEmail({
    to: assigneeUser.email,
    subject: `New Assignment: ${vulnerability.title}`,
    html: getAssignmentEmail(vulnerability.title, vulnerabilityId),
    text: `You have been assigned to vulnerability: ${vulnerability.title}`
  })
}

Notification Schema

// From prisma/schema.prisma:161
model Notification {
  id        String   @id @default(uuid())
  type      String   // VULNERABILITY_ASSIGNED, STATUS_CHANGED, COMMENT_ADDED
  title     String
  message   String
  read      Boolean  @default(false)
  link      String?  // Optional link to navigate to
  userId    String
  user      User     @relation(fields: [userId], references: [id])
  createdAt DateTime @default(now())
}

Managing Notifications

// From src/app/actions/notifications.ts:32
export async function markAsRead(notificationId: string) {
  await prisma.notification.update({
    where: { id: notificationId, userId: session.user.id },
    data: { read: true }
  })
}

export async function markAllAsRead() {
  await prisma.notification.updateMany({
    where: { userId: session.user.id, read: false },
    data: { read: true }
  })
}

Audit Logging

Audit logs are admin-only and scoped to team workspace. They provide complete traceability for compliance and security investigations.
// From src/app/actions/audit.ts:7
export async function getAuditLogs() {
  const session = await getServerSession(authOptions)
  
  if (session?.user?.role !== "ADMIN") {
    return { success: false, error: "Unauthorized" }
  }
  
  const admin = await prisma.user.findUnique({
    where: { id: session.user.id },
    select: { teamId: true }
  })
  
  const logs = await prisma.auditLog.findMany({
    where: { teamId: admin.teamId },
    orderBy: { createdAt: 'desc' },
    include: {
      user: { select: { email: true } }
    },
    take: 100 // Last 100 logs
  })
  
  return { success: true, data: logs }
}

Audit Log Schema

// From prisma/schema.prisma:103
model AuditLog {
  id          String   @id @default(uuid())
  action      String   // CREATE_VULNERABILITY, UPDATE_STATUS, ASSIGN, etc.
  entityType  String   // Vulnerability, User, Team
  entityId    String?
  details     String?  // JSON or text description
  userId      String
  user        User     @relation(fields: [userId], references: [id])
  teamId      String?
  team        Team?    @relation(fields: [teamId], references: [id])
  createdAt   DateTime @default(now())
}

Logged Actions

  • CREATE_VULNERABILITY: New vulnerability created
  • UPDATE_VULNERABILITY: Vulnerability modified
  • DELETE_VULNERABILITY: Vulnerability removed
  • UPDATE_STATUS: Status changed
  • ASSIGN_VULNERABILITY: Assignment changed
  • APPROVE_VULNERABILITY: Admin approval granted
  • ADD_COMMENT: Comment added
  • CREATE_USER: New team member added
  • UPDATE_USER_ROLE: Role changed
  • DELETE_USER: User removed from team

Creating Audit Entries

// From src/lib/audit.ts:5
export async function logAudit(
  action: string,
  entityType: string,
  entityId: string | null,
  details: string | null
) {
  const session = await getServerSession(authOptions)
  if (!session?.user?.id) return
  
  const user = await prisma.user.findUnique({
    where: { id: session.user.id },
    select: { teamId: true }
  })
  
  await prisma.auditLog.create({
    data: {
      action,
      entityType,
      entityId,
      details,
      userId: session.user.id,
      teamId: user?.teamId
    }
  })
}

Team Invitations

Invite new members via email:
// From prisma/schema.prisma:145
model Invitation {
  id        String   @id @default(uuid())
  email     String
  token     String   @unique
  role      String   @default("VIEWER")
  expiresAt DateTime
  
  teamId    String?
  team      Team?    @relation(fields: [teamId], references: [id])
  
  inviterId String
  inviter   User     @relation("UserInvitations", fields: [inviterId])
  
  createdAt DateTime @default(now())
}
Invitation links expire after 7 days. Invited users are automatically added to the inviter’s team workspace with the specified role.

User Management

Admins can manage team members:
  • Create Users: Add new team members directly
  • Update Roles: Change user permissions
  • Deactivate Users: Mark users as INACTIVE (preserves audit trail)
  • Delete Users: Permanently remove team members
Deleting a user does NOT delete their created vulnerabilities. Vulnerabilities remain assigned to the team.

Best Practices

  1. Assign Roles Carefully: Follow principle of least privilege
  2. Use Comments for Context: Document decisions and findings inline
  3. Monitor Notifications: Enable email alerts for critical vulnerabilities
  4. Review Audit Logs: Regular audit reviews for suspicious activity
  5. Invite via Email: Use invitation system rather than direct user creation
  6. Rotate Admin Access: Limit admin roles to essential personnel
  7. Document Approvals: Add comments when approving analyst submissions
  8. Track Assignments: Use assignment feature for accountability

Build docs developers (and LLMs) love