diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 15841124..3c1a994a 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -3,15 +3,15 @@ on: push: branches: - main - paths: - - "!frontend/**" + paths-ignore: + - "frontend/**" pull_request: - paths: - - "!frontend/**" + paths-ignore: + - "frontend/**" jobs: test: runs-on: - labels: ubuntu-latest-4-cores + labels: ubuntu-latest-8-cores steps: - uses: actions/checkout@v3 - uses: extractions/setup-just@v1 @@ -40,5 +40,3 @@ jobs: node-version: '18' - run: cd backend && yarn install - run: cd backend && yarn run lint - - diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 00000000..681311eb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 23b8ed94..2e7570e3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,8 +1,132 @@ -# Claude Code Configuration +# CLAUDE.md -## Frontend Tests +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +RocketAdmin is a database administration panel that allows users to manage database connections, tables, and data. It consists of multiple components in a monorepo structure: + +- **backend/** - NestJS API server (TypeScript, ES modules) +- **frontend/** - Angular 19 web application (standalone components) +- **rocketadmin-agent/** - NestJS agent for connecting to databases behind firewalls +- **autoadmin-ws-server/** - WebSocket server for agent communication +- **shared-code/** - Shared data access layer and utilities used by backend and agent + +## Development Commands + +### Backend + +```bash +cd backend +yarn start:dev # Start dev server with hot reload +yarn build # Build for production +yarn lint # ESLint with auto-fix +yarn test # Run non-saas AVA tests (serial) +yarn test-all # Run all AVA tests (5min timeout, serial) +yarn test-saas # Run SaaS-specific tests +``` + +### Frontend + +```bash +cd frontend +yarn start # Start Angular dev server +yarn build # Production build +yarn test:ci # Run tests headlessly (CI mode) +yarn test --browsers=ChromeHeadlessCustom --no-watch --no-progress # Headless tests +yarn lint # TSLint (deprecated, needs ESLint migration) +``` + +### Running Backend Tests with Docker + +The project uses `just` for test orchestration: -To run frontend tests: ```bash -cd frontend && yarn test --browsers=ChromeHeadlessCustom --no-watch --no-progress -``` \ No newline at end of file +just test # Run all backend tests with Docker Compose +just test "path/to/test.ts" # Run specific test file +``` + +This spins up test databases (MySQL, PostgreSQL, MSSQL, Oracle, IBM DB2, MongoDB, DynamoDB) via `docker-compose.tst.yml`. + +### Migrations + +```bash +cd backend +yarn build # Must build first +yarn migration:generate src/migrations/MigrationName # Generate migration +yarn migration:run # Run pending migrations +yarn migration:revert # Revert last migration +``` + +## Architecture + +### Monorepo Structure + +- Uses Yarn workspaces with packages: `backend`, `rocketadmin-agent`, `shared-code` +- `shared-code` is imported as `@rocketadmin/shared-code` workspace dependency +- Frontend is a separate Angular project (not a workspace member) + +### Backend (NestJS) + +- **Entities pattern**: Each entity has its own directory under `src/entities/` containing: + - `*.entity.ts` - TypeORM entity + - `*.module.ts` - NestJS module + - `*.controller.ts` - REST endpoints + - `*.service.ts` - Business logic (use cases) + - `dto/` - Request/response DTOs with class-validator decorators + - `*.controller.ee.ts` - Enterprise edition controllers (SaaS features) +- **Guards**: Authentication and authorization in `src/guards/` +- **Data access**: Uses `shared-code` for database operations via Knex +- **Testing**: AVA test framework with tests in `test/ava-tests/` + - `non-saas-tests/` - Core functionality tests + - `saas-tests/` - SaaS-specific feature tests + - `complex-table-tests/` - Complex table operation tests + +### Frontend (Angular 19) + +See `frontend/CLAUDE.md` for detailed frontend architecture. + +Key points: +- Standalone components (no NgModules) +- BehaviorSubject-based state management (no NgRx) +- Multi-environment builds (development, production, saas, saas-production) +- Jasmine/Karma testing with ChromeHeadless + +### Shared Code + +Located in `shared-code/src/`: +- `data-access-layer/` - Database abstraction supporting MySQL, PostgreSQL, MSSQL, Oracle, MongoDB, DynamoDB, IBM DB2, Cassandra, Elasticsearch +- `knex-manager/` - Knex connection management +- `caching/` - LRU cache utilities +- `helpers/` - Shared utilities + +### Agent Architecture + +The rocketadmin-agent connects to databases in private networks: +1. Agent runs inside customer's network +2. Connects to `autoadmin-ws-server` via WebSocket +3. Backend communicates with agent through WebSocket server +4. Agent executes database queries and returns results + +## Database Support + +The application supports: MySQL, PostgreSQL, MongoDB, DynamoDB, Cassandra, OracleDB, MSSQL, IBM DB2, Elasticsearch, Redis + +Database-specific DAOs are in `shared-code/src/data-access-layer/`. + +## Testing Database Connections + +Test databases are defined in `docker-compose.tst.yml`: +- MySQL: `testMySQL-e2e-testing:3306` +- PostgreSQL: `testPg-e2e-testing:5432` +- MSSQL: `mssql-e2e-testing:1433` +- Oracle: `test-oracle-e2e-testing:1521` +- IBM DB2: `test-ibm-db2-e2e-testing:50000` +- MongoDB: `test-mongo-e2e-testing:27017` +- DynamoDB: `test-dynamodb-e2e-testing:8000` + +## Coding Conventions + +### Class Member Ordering + +- Private methods must be placed at the end of the class, after all public methods diff --git a/Dockerfile b/Dockerfile index 1eb4e877..c7d02c01 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ COPY frontend/.yarn /app/frontend/.yarn RUN apt-get update && apt-get install -y \ git \ && rm -rf /var/lib/apt/lists/* -RUN yarn install --immutable --network-timeout 1000000 --silent +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --immutable --network-timeout 1000000 --silent COPY frontend/scripts /app/frontend/scripts COPY frontend/src /app/frontend/src @@ -40,13 +40,12 @@ COPY backend /app/backend COPY shared-code /app/shared-code COPY rocketadmin-agent /app/rocketadmin-agent COPY .yarn /app/.yarn -RUN yarn install --network-timeout 1000000 --immutable --silent +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --network-timeout 1000000 --immutable --silent RUN cd shared-code && ../node_modules/.bin/tsc -RUN cd backend && yarn run nest build +RUN cd backend && ../node_modules/.bin/tsc && yarn run nest build COPY --from=front_builder /app/frontend/dist/dissendium-v0 /var/www/html COPY frontend/nginx/default.conf /etc/nginx/sites-enabled/default - -RUN chown -R appuser:appuser /app +RUN mkdir -p /app/backend/node_modules/.cache && chown -R appuser:appuser /app/backend/node_modules/.cache RUN chown -R appuser:appuser /var/lib/nginx RUN chown -R appuser:appuser /var/log/nginx RUN chown -R appuser:appuser /run diff --git a/SECRET_STORAGE_SPECIFICATION.md b/SECRET_STORAGE_SPECIFICATION.md new file mode 100644 index 00000000..8c20b721 --- /dev/null +++ b/SECRET_STORAGE_SPECIFICATION.md @@ -0,0 +1,1809 @@ +# Secret Storage Feature Specification + +**Version:** 1.0 +**Date:** 2025-11-21 +**Status:** Draft + +## Table of Contents +1. [Overview](#overview) +2. [Goals and Objectives](#goals-and-objectives) +3. [Functional Requirements](#functional-requirements) +4. [Technical Design](#technical-design) +5. [Database Schema](#database-schema) +6. [API Specification](#api-specification) +7. [Security Design](#security-design) +8. [Frontend Requirements](#frontend-requirements) +9. [Implementation Phases](#implementation-phases) +10. [Testing Strategy](#testing-strategy) +11. [Migration and Rollout](#migration-and-rollout) +12. [Future Enhancements](#future-enhancements) + +--- + +## Overview + +### Context +RocketAdmin is a multi-database administration panel that currently stores encrypted database connection credentials. Users need a simple way to store and manage other sensitive information (API keys, tokens, certificates, passwords) related to their company's database work. + +### Problem Statement +Users currently have no secure way to: +- Store API keys for external services they use with their databases +- Manage authentication tokens needed for integrations +- Store certificates and encryption keys +- Audit access to sensitive information + +### Proposed Solution +Implement a simple, encrypted secret storage system that allows users to securely store, manage, and audit access to sensitive company information, leveraging RocketAdmin's existing encryption infrastructure. + +--- + +## Goals and Objectives + +### Primary Goals +1. **Secure Storage**: Provide military-grade encryption for company secrets +2. **User Experience**: Make secret management intuitive and seamless +3. **Access Control**: Company-based access only +4. **Audit Trail**: Track all access and modifications to secrets +5. **Integration**: Leverage existing encryption and authentication infrastructure + +### Success Criteria +- Users can create, read, update, and delete company secrets +- Secrets are encrypted at rest using existing infrastructure +- Master password protection available (optional) +- All secret access is logged +- Frontend provides intuitive UI similar to connection management +- Zero data breaches or unauthorized access incidents + +### Non-Goals (Out of Scope for v1) +- External vault integration (AWS Secrets Manager, HashiCorp Vault) +- Secret rotation automation +- Secret generators +- Secret versioning with full history +- Secret templates +- Bulk import/export +- User-to-user secret sharing +- Tags and categorization +- Secret types/categories + +--- + +## Functional Requirements + +### FR-1: Secret CRUD Operations + +#### FR-1.1: Create Secret +- **Actor**: Authenticated user +- **Preconditions**: User is logged in and belongs to a company +- **Flow**: + 1. User navigates to secrets page + 2. User clicks "Add Secret" + 3. User fills in secret details (slug, value) + 4. User optionally enables master password protection + 5. System validates input + 6. System encrypts secret + 7. System saves secret to database linked to user's company + 8. System logs creation event +- **Postconditions**: Secret is stored encrypted in database and associated with company +- **Validations**: + - Slug: 1-255 characters, letters (uppercase/lowercase), numbers, hyphens, underscores only, unique per company, required + - Value: 1-10000 characters, required + - Master password: If enabled, 8+ characters + +#### FR-1.2: Read Secret +- **Actor**: Authenticated user in the same company +- **Preconditions**: User belongs to the same company as the secret +- **Flow**: + 1. User navigates to secrets page + 2. User sees list of company secrets (slug only) + 3. User clicks on secret to view details + 4. If master password protected, system prompts for master password + 5. System validates user is in same company + 6. System decrypts secret + 7. System displays secret value (initially masked) + 8. User clicks "reveal" to show value + 9. System logs access event +- **Postconditions**: Secret access is logged +- **Security**: + - Secret value initially masked (****) + - Master password required if enabled + - Access logged with timestamp, IP, user agent + +#### FR-1.3: Update Secret +- **Actor**: Authenticated user (company member) +- **Preconditions**: User belongs to the same company as the secret +- **Flow**: + 1. User opens secret details + 2. User clicks "Edit" + 3. User modifies fields (can change value only, slug is immutable) + 4. User saves changes + 5. System validates input + 6. System re-encrypts secret (if value changed) + 7. System updates database + 8. System logs update event +- **Postconditions**: Secret is updated, audit log created +- **Validations**: Value: 1-10000 characters, required + +#### FR-1.4: Delete Secret +- **Actor**: Authenticated user (company member) +- **Preconditions**: User belongs to the same company as the secret +- **Flow**: + 1. User opens secret details + 2. User clicks "Delete" + 3. System prompts for confirmation + 4. User confirms deletion + 5. System permanently deletes secret from database + 6. System logs deletion event (before deletion) +- **Postconditions**: Secret is permanently deleted, audit log preserved +- **Security**: Any company member can delete company secrets + +### FR-2: Search Secrets + +#### FR-2.1: Search Secrets +- **Actor**: Authenticated user +- **Preconditions**: User is logged in +- **Flow**: + 1. User enters search query + 2. System searches slug only + 3. System returns matching secrets from user's company +- **Postconditions**: Filtered list displayed + +### FR-3: Secret Expiration + +#### FR-3.1: Set Secret Expiration +- **Actor**: Secret creator +- **Preconditions**: User created the secret +- **Flow**: + 1. User opens secret details + 2. User enables expiration + 3. User sets expiration date + 4. System saves expiration date +- **Postconditions**: Secret has expiration date +- **Validations**: Date must be in future + +#### FR-3.2: Handle Expired Secrets +- **Actor**: System (background job) +- **Preconditions**: N/A +- **Flow**: + 1. System runs daily job + 2. System finds secrets with expires_at < now + 3. System marks secrets as expired + 4. System sends notification to creator +- **Postconditions**: Expired secrets cannot be accessed +- **Behavior**: Expired secrets cannot be viewed until creator extends expiration + +### FR-4: Audit Logging + +#### FR-4.1: Log All Access +- **Actor**: System +- **Preconditions**: Any secret operation occurs +- **Flow**: + 1. User performs action (view, copy, update, delete) + 2. System captures event details + 3. System writes to audit log +- **Postconditions**: Audit record created +- **Logged Data**: + - Timestamp + - User ID + - Secret ID + - Action type + - IP address + - User agent + - Success/failure + +#### FR-4.2: View Audit Log +- **Actor**: Authenticated user (company member) +- **Preconditions**: User belongs to the same company as the secret +- **Flow**: + 1. User opens secret details + 2. User clicks "Audit Log" tab + 3. System displays access history +- **Postconditions**: User sees who accessed secret and when + +--- + +## Technical Design + +### Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Frontend (Angular) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Secrets List │ │Secret Details│ │ Share Dialog │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ HTTPS/JWT +┌─────────────────────────────────────────────────────────────┐ +│ Backend (NestJS) │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Secrets Module │ │ +│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │ │ +│ │ │ Controller │ │ Service │ │Repository │ │ │ +│ │ └──────────────┘ └──────────────┘ └───────────┘ │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Encryptor Service (Existing) │ │ +│ │ - encryptData() │ │ +│ │ - decryptData() │ │ +│ │ - encryptDataMasterPwd() │ │ +│ │ - decryptDataMasterPwd() │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Authorization Guards (Existing) │ │ +│ │ - JwtAuthGuard │ │ +│ │ - SecretAccessGuard (New) │ │ +│ └──────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ +┌─────────────────────────────────────────────────────────────┐ +│ PostgreSQL Database (TypeORM) │ +│ ┌───────────────┐ ┌──────────────────┐ ┌──────────────┐│ +│ │UserSecretEntity│ │SharedSecretEntity│ │SecretAccess ││ +│ │ │ │ │ │LogEntity ││ +│ └───────────────┘ └──────────────────┘ └──────────────┘│ +└─────────────────────────────────────────────────────────────┘ +``` + +### Module Structure + +``` +backend/src/ +├── entities/ +│ ├── user-secret/ +│ │ ├── user-secret.entity.ts +│ │ ├── user-secret.interface.ts +│ │ └── use-cases/ +│ │ ├── create-user-secret.use.case.ts +│ │ ├── update-user-secret.use.case.ts +│ │ ├── delete-user-secret.use.case.ts +│ │ ├── find-user-secrets.use.case.ts +│ │ ├── share-secret.use.case.ts +│ │ └── revoke-secret-access.use.case.ts +│ ├── shared-secret/ +│ │ ├── shared-secret.entity.ts +│ │ └── shared-secret.interface.ts +│ └── secret-access-log/ +│ ├── secret-access-log.entity.ts +│ └── secret-access-log.interface.ts +├── modules/ +│ └── secrets/ +│ ├── secrets.module.ts +│ ├── secrets.controller.ts +│ ├── secrets.service.ts +│ ├── dto/ +│ │ ├── create-secret.dto.ts +│ │ ├── update-secret.dto.ts +│ │ ├── share-secret.dto.ts +│ │ └── find-secrets.dto.ts +│ └── guards/ +│ └── secret-access.guard.ts +└── migrations/ + ├── TIMESTAMP-CreateUserSecretEntity.ts + ├── TIMESTAMP-CreateSharedSecretEntity.ts + └── TIMESTAMP-CreateSecretAccessLogEntity.ts +``` + +### Data Flow + +#### Creating a Secret +``` +1. User submits form → Frontend validates +2. Frontend sends POST /secrets with encrypted master password (if enabled) +3. SecretsController receives request +4. JwtAuthGuard validates authentication +5. SecretsService.createSecret() called +6. Encryptor.encryptData(value) encrypts with PRIVATE_KEY +7. If master password: Encryptor.encryptDataMasterPwd() double-encrypts +8. UserSecretEntity created and saved +9. SecretAccessLogEntity created (action: CREATE) +10. Response sent to frontend (secret without value) +``` + +#### Reading a Secret +``` +1. User clicks secret → Frontend requests GET /secrets/:id +2. If master password required, frontend prompts and sends in header +3. SecretsController receives request +4. JwtAuthGuard validates authentication +5. SecretAccessGuard validates permissions +6. SecretsService.findSecretById() called +7. Entity loaded from database +8. If master password: validate hash, decrypt with master password +9. Decrypt with PRIVATE_KEY +10. SecretAccessLogEntity created (action: VIEW) +11. Response sent with decrypted value +``` + +#### Sharing a Secret +``` +1. Owner clicks "Share" → Selects recipient and permissions +2. Frontend sends POST /secrets/:id/share +3. SecretsController receives request +4. Validates owner permission +5. Validates recipient exists +6. SharedSecretEntity created +7. SecretAccessLogEntity created (action: SHARE) +8. Notification sent to recipient +9. Response confirms sharing +``` + +--- + +## Database Schema + +### UserSecretEntity + +```typescript +@Entity('user_secrets') +@Index(['companyId', 'slug'], { unique: true }) +export class UserSecretEntity { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => CompanyInfoEntity) + @JoinColumn() + company: CompanyInfoEntity; + + @Column() + @Index() + companyId: string; + + @Column({ type: 'varchar', length: 255 }) + slug: string; + + @Column({ type: 'text' }) + encryptedValue: string; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + @Column({ type: 'timestamp', nullable: true }) + lastAccessedAt: Date; + + @Column({ type: 'timestamp', nullable: true }) + expiresAt: Date; + + @Column({ default: false }) + masterEncryption: boolean; + + @Column({ type: 'varchar', length: 255, nullable: true }) + masterHash: string; + + @OneToMany(() => SecretAccessLogEntity, (log) => log.secret) + accessLogs: SecretAccessLogEntity[]; + + @BeforeInsert() + @BeforeUpdate() + encryptCredentials() { + // Encrypt value with PRIVATE_KEY + if (this.encryptedValue && !this.masterEncryption) { + this.encryptedValue = Encryptor.encryptData(this.encryptedValue); + } + } + + @AfterLoad() + decryptCredentials() { + // Decrypt value with PRIVATE_KEY + if (this.encryptedValue && !this.masterEncryption) { + this.encryptedValue = Encryptor.decryptData(this.encryptedValue); + } + } +} +``` + +### SecretAccessLogEntity + +```typescript +@Entity('secret_access_logs') +export class SecretAccessLogEntity { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => UserSecretEntity, (secret) => secret.accessLogs) + @JoinColumn() + secret: UserSecretEntity; + + @Column() + @Index() + secretId: string; + + @ManyToOne(() => UserEntity) + @JoinColumn() + user: UserEntity; + + @Column() + @Index() + userId: string; + + @Column({ + type: 'enum', + enum: SecretActionEnum, + }) + action: SecretActionEnum; + + @CreateDateColumn() + @Index() + accessedAt: Date; + + @Column({ type: 'varchar', length: 45, nullable: true }) + ipAddress: string; + + @Column({ type: 'text', nullable: true }) + userAgent: string; + + @Column({ default: true }) + success: boolean; + + @Column({ type: 'text', nullable: true }) + errorMessage: string; +} + +export enum SecretActionEnum { + CREATE = 'create', + VIEW = 'view', + COPY = 'copy', + UPDATE = 'update', + DELETE = 'delete', +} +``` + +### Database Indexes + +```sql +-- Performance indexes +CREATE INDEX idx_user_secrets_company_id ON user_secrets(company_id); +CREATE INDEX idx_user_secrets_created_at ON user_secrets(created_at); +CREATE INDEX idx_user_secrets_expires_at ON user_secrets(expires_at); +CREATE UNIQUE INDEX idx_user_secrets_company_slug ON user_secrets(company_id, slug); + +CREATE INDEX idx_secret_access_logs_secret_id ON secret_access_logs(secret_id); +CREATE INDEX idx_secret_access_logs_user_id ON secret_access_logs(user_id); +CREATE INDEX idx_secret_access_logs_accessed_at ON secret_access_logs(accessed_at); +``` + +--- + +## API Specification + +### Base Path +All endpoints under: `/api/secrets` + +### Authentication +All endpoints require JWT authentication via `Authorization: Bearer ` header or `rocketadmin_cookie` cookie. + +### Master Password Header +When master password is enabled for a secret: `masterpwd: ` + +### Endpoints + +#### 1. Create Secret +```http +POST /api/secrets +Content-Type: application/json +Authorization: Bearer +masterpwd: (optional) + +{ + "slug": "aws-api-key", + "value": "AKIAIOSFODNN7EXAMPLE", + "expiresAt": "2026-12-31T23:59:59Z", + "masterEncryption": true, + "masterPassword": "MyStrongPassword123!" +} + +Response: 201 Created +{ + "id": "uuid", + "slug": "aws-api-key", + "createdAt": "2025-11-21T10:00:00Z", + "updatedAt": "2025-11-21T10:00:00Z", + "expiresAt": "2026-12-31T23:59:59Z", + "masterEncryption": true, + "companyId": "company-uuid" +} + +Error: 409 Conflict (if slug already exists in company) +{ + "statusCode": 409, + "message": "Secret with this slug already exists in your company", + "error": "Conflict" +} +``` + +#### 2. Get All Secrets (List) +```http +GET /api/secrets?page=1&limit=20&search=aws +Authorization: Bearer + +Response: 200 OK +{ + "data": [ + { + "id": "uuid", + "slug": "aws-api-key", + "createdAt": "2025-11-21T10:00:00Z", + "updatedAt": "2025-11-21T10:00:00Z", + "lastAccessedAt": "2025-11-21T11:30:00Z", + "expiresAt": "2026-12-31T23:59:59Z", + "masterEncryption": true, + "createdBy": { + "id": "user-uuid", + "email": "user@example.com", + "name": "John Doe" + } + } + ], + "pagination": { + "total": 15, + "page": 1, + "limit": 20, + "totalPages": 1 + } +} +``` + +#### 3. Get Secret by Slug +```http +GET /api/secrets/:slug +Authorization: Bearer +masterpwd: (if master encryption enabled) + +Response: 200 OK +{ + "id": "uuid", + "slug": "aws-api-key", + "value": "AKIAIOSFODNN7EXAMPLE", + "createdAt": "2025-11-21T10:00:00Z", + "updatedAt": "2025-11-21T10:00:00Z", + "lastAccessedAt": "2025-11-21T11:30:00Z", + "expiresAt": "2026-12-31T23:59:59Z", + "masterEncryption": true, + "companyId": "company-uuid", + "createdBy": { + "id": "user-uuid", + "email": "user@example.com", + "name": "John Doe" + } +} + +Error: 403 Forbidden (if master password required but not provided or incorrect) +{ + "statusCode": 403, + "message": "Master password required", + "error": "Forbidden" +} +``` + +#### 4. Update Secret +```http +PUT /api/secrets/:slug +Content-Type: application/json +Authorization: Bearer +masterpwd: (if currently encrypted) + +{ + "value": "NEWAKIAIOSFODNN7EXAMPLE", + "expiresAt": "2027-12-31T23:59:59Z" +} + +Response: 200 OK +{ + "id": "uuid", + "slug": "aws-api-key", + "updatedAt": "2025-11-21T12:00:00Z", + "expiresAt": "2027-12-31T23:59:59Z" +} +``` + +#### 5. Delete Secret +```http +DELETE /api/secrets/:slug +Authorization: Bearer + +Response: 200 OK +{ + "message": "Secret deleted successfully", + "deletedAt": "2025-11-21T12:30:00Z" +} + +Error: 403 Forbidden (if not company member) +{ + "statusCode": 403, + "message": "You don't have permission to delete this secret", + "error": "Forbidden" +} +``` + +#### 6. Get Secret Audit Log +```http +GET /api/secrets/:slug/audit-log?page=1&limit=50 +Authorization: Bearer + +Response: 200 OK +{ + "data": [ + { + "id": "log-uuid", + "action": "view", + "user": { + "id": "user-uuid", + "email": "user@example.com", + "name": "John Doe" + }, + "accessedAt": "2025-11-21T11:30:00Z", + "ipAddress": "192.168.1.100", + "userAgent": "Mozilla/5.0...", + "success": true + } + ], + "pagination": { + "total": 25, + "page": 1, + "limit": 50, + "totalPages": 1 + } +} +``` + +### Error Responses + +```http +400 Bad Request - Validation error +{ + "statusCode": 400, + "message": ["slug should not be empty", "slug must match pattern ^[a-zA-Z0-9_-]+$", "value should not be empty"], + "error": "Bad Request" +} + +401 Unauthorized - Not authenticated +{ + "statusCode": 401, + "message": "Unauthorized", + "error": "Unauthorized" +} + +403 Forbidden - No permission +{ + "statusCode": 403, + "message": "You don't have permission to access this secret", + "error": "Forbidden" +} + +404 Not Found - Secret not found +{ + "statusCode": 404, + "message": "Secret not found", + "error": "Not Found" +} + +409 Conflict - Slug already exists +{ + "statusCode": 409, + "message": "Secret with this slug already exists in your company", + "error": "Conflict" +} + +410 Gone - Secret expired +{ + "statusCode": 410, + "message": "Secret has expired", + "error": "Gone" +} +``` + +--- + +## Security Design + +### Encryption Architecture + +#### Layer 1: Base Encryption (Always Active) +- **Algorithm**: AES-256 +- **Key**: `PRIVATE_KEY` environment variable (64+ characters) +- **Implementation**: `Encryptor.encryptData(value)` +- **Applied**: All secret values encrypted at rest +- **Lifecycle**: Encrypted on `@BeforeInsert/@BeforeUpdate`, decrypted on `@AfterLoad` + +#### Layer 2: Master Password Encryption (Optional) +- **Algorithm**: AES-256 +- **Key**: User-provided master password +- **Implementation**: `Encryptor.encryptDataMasterPwd(value, masterPwd)` +- **Applied**: When user enables `masterEncryption` flag +- **Validation**: Master password hash stored in `masterHash` field (PBKDF2) +- **Process**: Value encrypted with master password FIRST, then with PRIVATE_KEY + +#### Encryption Flow +``` +Plain Value → [Master Password Encrypt] → [PRIVATE_KEY Encrypt] → Stored in DB + +Retrieval: +Stored Value → [PRIVATE_KEY Decrypt] → [Master Password Decrypt] → Plain Value +``` + +### Access Control + +#### Permission Matrix + +| Action | Company Member | Non-member | +|--------|----------------|------------| +| View | ✓ | ✗ | +| Edit | ✓ | ✗ | +| Delete | ✓ | ✗ | +| Audit | ✓ | ✗ | + +#### SecretAccessGuard Implementation + +```typescript +@Injectable() +export class SecretAccessGuard implements CanActivate { + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const user = request.user; // From JWT + const slug = request.params.slug; + + // Load secret by slug and company + const secret = await this.secretsRepository.findOne({ + where: { slug, companyId: user.companyId }, + relations: ['company'], + }); + + if (!secret) { + throw new NotFoundException('Secret not found'); + } + + // Check expiration + if (secret.expiresAt && secret.expiresAt < new Date()) { + throw new GoneException('Secret has expired'); + } + + // All company members have full access + return true; + } +} +``` + +### Audit Logging + +#### Log Creation Service + +```typescript +@Injectable() +export class SecretAuditService { + async logAccess( + secretId: string, + userId: string, + action: SecretActionEnum, + request: Request, + success: boolean = true, + errorMessage?: string, + ): Promise { + const log = new SecretAccessLogEntity(); + log.secretId = secretId; + log.userId = userId; + log.action = action; + log.accessedAt = new Date(); + log.ipAddress = this.getClientIp(request); + log.userAgent = request.headers['user-agent']; + log.success = success; + log.errorMessage = errorMessage; + + await this.secretAccessLogRepository.save(log); + + // Update lastAccessedAt on secret for VIEW actions + if (action === SecretActionEnum.VIEW && success) { + await this.secretRepository.update(secretId, { + lastAccessedAt: new Date(), + }); + } + } + + private getClientIp(request: Request): string { + return ( + (request.headers['x-forwarded-for'] as string)?.split(',')[0] || + (request.headers['x-real-ip'] as string) || + request.connection.remoteAddress || + request.socket.remoteAddress || + 'unknown' + ); + } +} +``` + +### Master Password Security + +#### Frontend Handling (CRITICAL CHANGE) +**Current Problem**: Master passwords stored in localStorage (vulnerable to XSS) + +**Solution**: +1. **Never store master passwords in localStorage** +2. **Session-only storage**: + ```typescript + // Store in memory only (component state) + private masterPasswords: Map = new Map(); + + // Or use sessionStorage with auto-clear + sessionStorage.setItem(`master_${secretId}`, password); + // Clear after 15 minutes + setTimeout(() => { + sessionStorage.removeItem(`master_${secretId}`); + }, 15 * 60 * 1000); + ``` + +3. **Re-prompt after timeout**: + - Store timestamp of last entry + - Require re-entry after 15 minutes + - Clear on browser close (sessionStorage) + +4. **Optional: "Remember for session" checkbox**: + - User must explicitly opt-in + - Still clears on browser close + - Never persist to localStorage + +#### Backend Validation + +```typescript +async validateMasterPassword( + secret: UserSecretEntity, + providedPassword: string, +): Promise { + if (!secret.masterEncryption) { + return true; + } + + if (!providedPassword) { + throw new ForbiddenException('Master password required'); + } + + const passwordValid = Encryptor.verifyUserPassword( + providedPassword, + secret.masterHash, + ); + + if (!passwordValid) { + // Log failed attempt + await this.auditService.logAccess( + secret.id, + 'current-user-id', + SecretActionEnum.VIEW, + request, + false, + 'Invalid master password', + ); + throw new ForbiddenException('Invalid master password'); + } + + return true; +} +``` + +### Rate Limiting + +Apply rate limits to prevent brute force attacks on master passwords: + +```typescript +@Controller('secrets') +@UseGuards(JwtAuthGuard) +export class SecretsController { + @Get(':id') + @Throttle(10, 60) // 10 requests per 60 seconds + async findOne( + @Param('id') id: string, + @Headers('masterpwd') masterPwd: string, + ) { + // ... + } +} +``` + +### Input Sanitization + +```typescript +export class CreateSecretDto { + @IsString() + @IsNotEmpty() + @MinLength(1) + @MaxLength(255) + @Matches(/^[a-zA-Z0-9_-]+$/, { + message: 'slug must contain only letters, numbers, hyphens, and underscores' + }) + @Transform(({ value }) => value.trim()) + slug: string; + + @IsString() + @IsNotEmpty() + @MinLength(1) + @MaxLength(10000) + value: string; // Don't trim (may be intentional whitespace) + + @IsOptional() + @IsISO8601() + @IsDateInFuture() + expiresAt?: string; + + @IsBoolean() + @IsOptional() + masterEncryption?: boolean; + + @IsString() + @IsOptional() + @MinLength(8) + @ValidateIf((o) => o.masterEncryption === true) + masterPassword?: string; +} +``` + +### SQL Injection Prevention +- **TypeORM** handles parameterization automatically +- Never use raw queries with user input +- Use QueryBuilder for complex queries + +### XSS Prevention +- Frontend sanitizes all output using Angular's built-in DomSanitizer +- Content-Security-Policy headers set +- Secret values displayed in `
` tags or ``
+
+---
+
+## Frontend Requirements
+
+### New Components
+
+#### 1. Secrets List Component
+**Path**: `frontend/src/app/components/secrets/secrets-list/secrets-list.component.ts`
+
+**Features**:
+- Displays company secrets in table/card view
+- Columns: Slug, Last Accessed, Expires, Actions
+- Search bar (filters by slug only)
+- Pagination
+- Action buttons: View, Edit, Delete (all available to company members)
+
+#### 2. Secret Details Component
+**Path**: `frontend/src/app/components/secrets/secret-details/secret-details.component.ts`
+
+**Features**:
+- Displays secret metadata
+- Secret value initially masked (****)
+- "Reveal" button to show value
+- "Copy to Clipboard" button with auto-clear after 30 seconds
+- Master password prompt dialog (if required)
+- Edit mode (available to all company members)
+- Audit log tab showing access history (available to all company members)
+
+#### 3. Create/Edit Secret Dialog
+**Path**: `frontend/src/app/components/secrets/secret-form-dialog/secret-form-dialog.component.ts`
+
+**Features**:
+- Form fields: Slug (disabled on edit), Value
+- Slug validation (letters, numbers, hyphens, underscores only)
+- Master password toggle
+- Master password input (confirmation required)
+- Expiration date picker
+- Validation error display
+
+#### 4. Master Password Prompt Dialog
+**Path**: `frontend/src/app/components/secrets/master-password-prompt/master-password-prompt.component.ts`
+
+**Features**:
+- Password input field
+- "Remember for session" checkbox (stores in sessionStorage)
+- Cancel/Submit buttons
+- Error message display
+
+#### 5. Audit Log Component
+**Path**: `frontend/src/app/components/secrets/audit-log/audit-log.component.ts`
+
+**Features**:
+- Table with columns: User, Action, Timestamp, IP Address, Status
+- Pagination
+- Filter by action type
+- Date range picker
+
+### Navigation Updates
+
+Add "Secrets" menu item to main navigation:
+```typescript
+// frontend/src/app/app-routing.module.ts
+{
+  path: 'secrets',
+  loadChildren: () => import('./secrets/secrets.module').then(m => m.SecretsModule),
+  canActivate: [AuthGuard],
+}
+```
+
+### Service Layer
+
+#### SecretsService
+**Path**: `frontend/src/app/services/secrets.service.ts`
+
+```typescript
+@Injectable({ providedIn: 'root' })
+export class SecretsService {
+  private apiUrl = '/api/secrets';
+
+  constructor(private http: HttpClient) {}
+
+  getSecrets(params?: SecretListParams): Observable {
+    return this.http.get(this.apiUrl, { params });
+  }
+
+  getSecretById(id: string, masterPassword?: string): Observable {
+    const headers = masterPassword ? { masterpwd: masterPassword } : {};
+    return this.http.get(`${this.apiUrl}/${id}`, { headers });
+  }
+
+  createSecret(secret: CreateSecretDto): Observable {
+    return this.http.post(this.apiUrl, secret);
+  }
+
+  updateSecret(id: string, secret: UpdateSecretDto, masterPassword?: string): Observable {
+    const headers = masterPassword ? { masterpwd: masterPassword } : {};
+    return this.http.put(`${this.apiUrl}/${id}`, secret, { headers });
+  }
+
+  deleteSecret(id: string): Observable {
+    return this.http.delete(`${this.apiUrl}/${id}`);
+  }
+
+  getAuditLog(id: string, page: number = 1): Observable {
+    return this.http.get(`${this.apiUrl}/${id}/audit-log`, {
+      params: { page: page.toString(), limit: '50' },
+    });
+  }
+}
+```
+
+#### MasterPasswordManager
+**Path**: `frontend/src/app/services/master-password-manager.service.ts`
+
+```typescript
+@Injectable({ providedIn: 'root' })
+export class MasterPasswordManager {
+  private passwords = new Map();
+  private readonly TIMEOUT_MINUTES = 15;
+
+  storePassword(secretId: string, password: string, rememberForSession: boolean): void {
+    if (!rememberForSession) {
+      // Store in memory only
+      this.passwords.set(secretId, {
+        password,
+        expiresAt: new Date(Date.now() + this.TIMEOUT_MINUTES * 60 * 1000),
+      });
+      return;
+    }
+
+    // Store in sessionStorage (cleared on browser close)
+    const data = {
+      password,
+      expiresAt: Date.now() + this.TIMEOUT_MINUTES * 60 * 1000,
+    };
+    sessionStorage.setItem(`master_${secretId}`, JSON.stringify(data));
+
+    // Also store in memory for quick access
+    this.passwords.set(secretId, {
+      password,
+      expiresAt: new Date(data.expiresAt),
+    });
+  }
+
+  getPassword(secretId: string): string | null {
+    // Check memory first
+    const memoryEntry = this.passwords.get(secretId);
+    if (memoryEntry && memoryEntry.expiresAt > new Date()) {
+      return memoryEntry.password;
+    }
+
+    // Check sessionStorage
+    const stored = sessionStorage.getItem(`master_${secretId}`);
+    if (stored) {
+      try {
+        const data = JSON.parse(stored);
+        if (data.expiresAt > Date.now()) {
+          return data.password;
+        }
+        // Expired, remove
+        sessionStorage.removeItem(`master_${secretId}`);
+      } catch (e) {
+        sessionStorage.removeItem(`master_${secretId}`);
+      }
+    }
+
+    // Expired or not found
+    this.passwords.delete(secretId);
+    return null;
+  }
+
+  clearPassword(secretId: string): void {
+    this.passwords.delete(secretId);
+    sessionStorage.removeItem(`master_${secretId}`);
+  }
+
+  clearAll(): void {
+    this.passwords.clear();
+    // Clear all master passwords from sessionStorage
+    Object.keys(sessionStorage)
+      .filter(key => key.startsWith('master_'))
+      .forEach(key => sessionStorage.removeItem(key));
+  }
+}
+```
+
+### UI/UX Considerations
+
+1. **Copy to Clipboard**:
+   - Show success toast
+   - Auto-clear clipboard after 30 seconds (optional)
+   - Use Clipboard API with fallback
+
+2. **Secret Value Display**:
+   - Initially show as `••••••••••••`
+   - "Reveal" button changes to "Hide"
+   - When revealed, show in monospace font
+   - Use `` for easy reveal/hide toggle
+
+3. **Master Password UX**:
+   - Prompt appears as modal dialog
+   - Show "forgot password" hint: "Contact secret owner"
+   - After 3 failed attempts, show captcha or rate limit
+
+4. **Expiration Warnings**:
+   - Show badge if expires within 7 days
+   - Show different badge if expired
+   - Toast notification when secret is about to expire
+
+5. **Responsive Design**:
+   - Mobile-friendly table (convert to cards)
+   - Touch-friendly buttons
+   - Full-screen dialogs on mobile
+
+6. **Accessibility**:
+   - ARIA labels for all interactive elements
+   - Keyboard navigation support
+   - Screen reader announcements
+   - High contrast mode support
+
+---
+
+## Implementation Phases
+
+### Phase 1: Backend Foundation (Week 1-2)
+**Goal**: Core backend functionality
+
+**Tasks**:
+1. Create TypeORM entities:
+   - UserSecretEntity
+   - SecretAccessLogEntity
+2. Create database migrations
+3. Implement SecretsService with CRUD methods
+4. Implement encryption/decryption using existing Encryptor
+5. Implement SecretAccessGuard
+6. Create SecretsController with endpoints:
+   - POST /secrets
+   - GET /secrets
+   - GET /secrets/:id
+   - PUT /secrets/:id
+   - DELETE /secrets/:id
+   - GET /secrets/:id/audit-log
+7. Add audit logging to all operations
+8. Write unit tests for service layer
+9. Write e2e tests for API endpoints
+
+**Deliverables**:
+- Working API for secret CRUD
+- Encrypted storage
+- Company-based access control
+- Audit logging
+- Test coverage >80%
+
+### Phase 2: Frontend Implementation (Week 3-4)
+**Goal**: Complete user interface
+
+**Tasks**:
+1. Create Angular module and routing
+2. Implement SecretsService (HTTP client)
+3. Implement MasterPasswordManager
+4. Create components:
+   - secrets-list
+   - secret-details
+   - secret-form-dialog
+   - master-password-prompt
+   - audit-log
+5. Add navigation menu item
+6. Implement responsive design
+7. Add loading states and error handling
+8. Write frontend unit tests
+
+**Deliverables**:
+- Complete UI for all features
+- Responsive design
+- Error handling
+- Frontend tests
+
+### Phase 3: Polish & Testing (Week 5)
+**Goal**: Final touches and production readiness
+
+**Tasks**:
+1. Implement secret expiration handling
+2. Add expiration notifications
+3. UI polish and refinements
+4. Performance optimization
+5. Security audit
+6. Documentation
+7. Integration testing
+8. Load testing
+
+**Deliverables**:
+- Expiration system complete
+- Documentation complete
+- Ready for production
+
+### Phase 4: Launch (Week 6)
+**Goal**: Production deployment
+
+**Tasks**:
+1. Security penetration testing
+2. User acceptance testing
+3. Deploy to staging
+4. Monitor and fix issues
+5. Deploy to production
+6. Post-launch monitoring
+
+**Deliverables**:
+- Stable production deployment
+- Zero critical bugs
+- Monitoring dashboards
+
+---
+
+## Testing Strategy
+
+### Unit Tests
+
+#### Backend
+```typescript
+// secrets.service.spec.ts
+describe('SecretsService', () => {
+  it('should create a secret with encryption', async () => {
+    const dto = { title: 'Test', value: 'secret', secretType: 'api_key' };
+    const result = await service.createSecret(dto, user);
+    expect(result.encryptedValue).not.toBe('secret');
+    expect(result.title).toBe('Test');
+  });
+
+  it('should decrypt secret value when loading', async () => {
+    const secret = await service.findById(secretId, user);
+    expect(secret.value).toBe('original_value');
+  });
+
+  it('should require master password when enabled', async () => {
+    await expect(
+      service.findById(secretId, user, null)
+    ).rejects.toThrow('Master password required');
+  });
+
+  it('should log all access attempts', async () => {
+    await service.findById(secretId, user);
+    const logs = await auditService.getLogs(secretId);
+    expect(logs.length).toBe(1);
+    expect(logs[0].action).toBe('view');
+  });
+});
+
+// secret-access.guard.spec.ts
+describe('SecretAccessGuard', () => {
+  it('should allow owner full access', async () => {
+    const canActivate = await guard.canActivate(context);
+    expect(canActivate).toBe(true);
+  });
+
+  it('should allow shared read access', async () => {
+    const canActivate = await guard.canActivate(context);
+    expect(canActivate).toBe(true);
+  });
+
+  it('should deny shared write access for read-only share', async () => {
+    await expect(guard.canActivate(context)).rejects.toThrow('read-only access');
+  });
+
+  it('should deny access to non-members', async () => {
+    await expect(guard.canActivate(context)).rejects.toThrow('permission');
+  });
+});
+```
+
+#### Frontend
+```typescript
+// secrets.service.spec.ts
+describe('SecretsService', () => {
+  it('should fetch secrets list', (done) => {
+    service.getSecrets().subscribe(response => {
+      expect(response.data.length).toBeGreaterThan(0);
+      done();
+    });
+  });
+
+  it('should send master password in header', (done) => {
+    service.getSecretById('id', 'password').subscribe(() => {
+      const req = httpMock.expectOne('/api/secrets/id');
+      expect(req.request.headers.get('masterpwd')).toBe('password');
+      done();
+    });
+  });
+});
+
+// master-password-manager.spec.ts
+describe('MasterPasswordManager', () => {
+  it('should store password in memory', () => {
+    manager.storePassword('secret-id', 'password', false);
+    expect(manager.getPassword('secret-id')).toBe('password');
+  });
+
+  it('should expire password after timeout', fakeAsync(() => {
+    manager.storePassword('secret-id', 'password', false);
+    tick(16 * 60 * 1000); // 16 minutes
+    expect(manager.getPassword('secret-id')).toBeNull();
+  }));
+
+  it('should clear all passwords', () => {
+    manager.storePassword('secret-1', 'password', false);
+    manager.storePassword('secret-2', 'password', false);
+    manager.clearAll();
+    expect(manager.getPassword('secret-1')).toBeNull();
+    expect(manager.getPassword('secret-2')).toBeNull();
+  });
+});
+```
+
+### Integration Tests
+
+```typescript
+// secrets.e2e.spec.ts
+describe('Secrets API (e2e)', () => {
+  it('should create, read, update, and delete secret', async () => {
+    // Create
+    const createResponse = await request(app.getHttpServer())
+      .post('/api/secrets')
+      .set('Authorization', `Bearer ${jwtToken}`)
+      .send({ title: 'Test Secret', value: 'secret123', secretType: 'api_key' })
+      .expect(201);
+
+    const secretId = createResponse.body.id;
+
+    // Read
+    const readResponse = await request(app.getHttpServer())
+      .get(`/api/secrets/${secretId}`)
+      .set('Authorization', `Bearer ${jwtToken}`)
+      .expect(200);
+
+    expect(readResponse.body.value).toBe('secret123');
+
+    // Update
+    await request(app.getHttpServer())
+      .put(`/api/secrets/${secretId}`)
+      .set('Authorization', `Bearer ${jwtToken}`)
+      .send({ title: 'Updated Secret', value: 'newsecret', secretType: 'api_key' })
+      .expect(200);
+
+    // Delete
+    await request(app.getHttpServer())
+      .delete(`/api/secrets/${secretId}`)
+      .set('Authorization', `Bearer ${jwtToken}`)
+      .expect(200);
+  });
+
+  it('should enforce master password protection', async () => {
+    const secretId = await createSecretWithMasterPassword('password123');
+
+    // Should fail without password
+    await request(app.getHttpServer())
+      .get(`/api/secrets/${secretId}`)
+      .set('Authorization', `Bearer ${jwtToken}`)
+      .expect(403);
+
+    // Should succeed with correct password
+    await request(app.getHttpServer())
+      .get(`/api/secrets/${secretId}`)
+      .set('Authorization', `Bearer ${jwtToken}`)
+      .set('masterpwd', 'password123')
+      .expect(200);
+  });
+
+  it('should allow company members to view secrets', async () => {
+    const secret = await createSecret(user1);
+
+    // User2 from same company should be able to read
+    await request(app.getHttpServer())
+      .get(`/api/secrets/${secret.id}`)
+      .set('Authorization', `Bearer ${user2Token}`)
+      .expect(200);
+
+    // User2 should NOT be able to edit
+    await request(app.getHttpServer())
+      .put(`/api/secrets/${secret.id}`)
+      .set('Authorization', `Bearer ${user2Token}`)
+      .send({ title: 'Updated', value: 'newvalue' })
+      .expect(403);
+
+    // User from different company should NOT have access
+    await request(app.getHttpServer())
+      .get(`/api/secrets/${secret.id}`)
+      .set('Authorization', `Bearer ${user3DifferentCompanyToken}`)
+      .expect(403);
+  });
+});
+```
+
+### Security Tests
+
+```typescript
+// security.e2e.spec.ts
+describe('Security Tests', () => {
+  it('should prevent SQL injection in search', async () => {
+    const maliciousQuery = "'; DROP TABLE user_secrets; --";
+    await request(app.getHttpServer())
+      .get('/api/secrets')
+      .query({ search: maliciousQuery })
+      .set('Authorization', `Bearer ${jwtToken}`)
+      .expect(200); // Should not cause error
+
+    // Verify table still exists
+    const count = await secretRepository.count();
+    expect(count).toBeGreaterThan(0);
+  });
+
+  it('should prevent XSS in secret values', async () => {
+    const xssPayload = '';
+    const secret = await service.createSecret({
+      title: xssPayload,
+      value: xssPayload,
+      secretType: 'other',
+    }, user);
+
+    const retrieved = await service.findById(secret.id, user);
+    expect(retrieved.title).toBe(xssPayload); // Stored as-is
+    // Frontend should sanitize when displaying
+  });
+
+  it('should rate limit master password attempts', async () => {
+    const secretId = await createSecretWithMasterPassword('correct');
+
+    // Try 20 times with wrong password
+    const requests = [];
+    for (let i = 0; i < 20; i++) {
+      requests.push(
+        request(app.getHttpServer())
+          .get(`/api/secrets/${secretId}`)
+          .set('Authorization', `Bearer ${jwtToken}`)
+          .set('masterpwd', 'wrong')
+      );
+    }
+
+    const responses = await Promise.all(requests);
+    const tooManyRequests = responses.filter(r => r.status === 429);
+    expect(tooManyRequests.length).toBeGreaterThan(0);
+  });
+
+  it('should not leak secret existence in 404 vs 403', async () => {
+    const nonExistentId = '00000000-0000-0000-0000-000000000000';
+
+    // Non-existent secret
+    const res1 = await request(app.getHttpServer())
+      .get(`/api/secrets/${nonExistentId}`)
+      .set('Authorization', `Bearer ${jwtToken}`)
+      .expect(404);
+
+    // Existing secret, no permission
+    const secretId = await createSecretForDifferentUser();
+    const res2 = await request(app.getHttpServer())
+      .get(`/api/secrets/${secretId}`)
+      .set('Authorization', `Bearer ${jwtToken}`)
+      .expect(404); // Should also be 404, not 403
+
+    // Both should have same message
+    expect(res1.body.message).toBe(res2.body.message);
+  });
+});
+```
+
+### Performance Tests
+
+```typescript
+// performance.spec.ts
+describe('Performance Tests', () => {
+  it('should handle 1000 secrets efficiently', async () => {
+    // Create 1000 secrets
+    await Promise.all(
+      Array.from({ length: 1000 }, (_, i) =>
+        service.createSecret({
+          title: `Secret ${i}`,
+          value: `value${i}`,
+          secretType: 'api_key',
+        }, user)
+      )
+    );
+
+    // List should be fast
+    const start = Date.now();
+    await service.findAll({ userId: user.id, page: 1, limit: 20 });
+    const duration = Date.now() - start;
+    expect(duration).toBeLessThan(100); // <100ms
+  });
+
+  it('should decrypt secrets in parallel', async () => {
+    const secretIds = await createMultipleSecrets(100);
+
+    const start = Date.now();
+    await Promise.all(secretIds.map(id => service.findById(id, user)));
+    const duration = Date.now() - start;
+
+    expect(duration).toBeLessThan(1000); // <1s for 100 secrets
+  });
+});
+```
+
+---
+
+## Migration and Rollout
+
+### Database Migration Strategy
+
+#### Step 1: Deploy Migration (Backward Compatible)
+```bash
+# Run migrations
+npm run migration:run
+
+# Verify tables created
+npm run migration:show
+```
+
+#### Step 2: Deploy Backend (Feature Flag)
+```typescript
+// Add feature flag to environment
+SECRETS_FEATURE_ENABLED=true
+
+// In secrets.controller.ts
+@Controller('secrets')
+@UseGuards(FeatureFlagGuard('SECRETS_FEATURE_ENABLED'))
+export class SecretsController {
+  // ...
+}
+```
+
+#### Step 3: Deploy Frontend (Progressive Rollout)
+```typescript
+// Use feature flag service
+if (this.featureFlags.isEnabled('secrets')) {
+  // Show secrets menu item
+}
+```
+
+#### Step 4: Enable for Beta Users
+- Enable for internal team first
+- Monitor for errors and performance issues
+- Gather feedback
+
+#### Step 5: Full Rollout
+- Enable for 10% of users
+- Monitor metrics (error rates, usage, performance)
+- Gradually increase to 100%
+
+### Rollback Plan
+
+If critical issues occur:
+
+1. **Disable Feature Flag**:
+   ```bash
+   # Backend
+   SECRETS_FEATURE_ENABLED=false
+
+   # Frontend
+   Feature flag service will hide UI
+   ```
+
+2. **Data Remains Safe**:
+   - Database tables remain
+   - Data is encrypted and safe
+   - No data loss
+
+3. **Fix and Redeploy**:
+   - Fix issues in staging
+   - Re-enable feature flag
+
+### Migration from Beta (If Applicable)
+
+If secrets were stored elsewhere (e.g., connection descriptions):
+
+```typescript
+// Migration script: migrate-legacy-secrets.ts
+async function migrateLegacySecrets() {
+  // Find connections with secrets in description
+  const connections = await connectionRepository.find({
+    where: { description: Like('%API_KEY:%') },
+  });
+
+  for (const connection of connections) {
+    // Parse secrets from description
+    const secrets = parseSecretsFromDescription(connection.description);
+
+    // Create new secret entities
+    for (const secret of secrets) {
+      await secretRepository.save({
+        userId: connection.authorId,
+        title: `${connection.title} - ${secret.name}`,
+        value: secret.value,
+        secretType: 'api_key',
+        tags: ['migrated', connection.title],
+      });
+    }
+
+    // Remove from description
+    connection.description = removeSecretsFromDescription(connection.description);
+    await connectionRepository.save(connection);
+  }
+}
+```
+
+---
+
+## Future Enhancements
+
+### Phase 2 Features (Post-Launch)
+
+#### 1. User-to-User Secret Sharing
+- Share secrets with specific users
+- Permission levels (read-only, read-write)
+- Share expiration dates
+- Revoke access
+- View who has access to each secret
+
+#### 2. Tags and Categorization
+- Add multiple tags to secrets
+- Filter by tags
+- Organize secrets by category
+- Tag-based search
+
+#### 3. Secret Types
+- Predefined secret types (API Key, Token, Password, Certificate, SSH Key, etc.)
+- Type-specific validation
+- Filter by secret type
+- Type-based templates
+
+#### 4. Descriptions
+- Add detailed descriptions to secrets
+- Search by description
+- Rich text support
+
+#### 5. External Vault Integration
+- **AWS Secrets Manager**: Store/retrieve secrets from AWS
+- **HashiCorp Vault**: Enterprise secret management
+- **Azure Key Vault**: Azure secret storage
+- Configuration per company or per secret
+
+#### 6. Secret Versioning
+- Keep history of secret changes
+- Restore previous versions
+- Diff view between versions
+
+#### 7. Secret Rotation
+- Automatic expiration warnings
+- Integration with external APIs for rotation
+- Rotation policies
+
+#### 8. Secret Templates
+- Predefined templates for common services (AWS, GitHub, Stripe)
+- Auto-populate fields
+- Validation for each template
+
+#### 9. Secret Generators
+- Password generator with strength indicator
+- API key generator
+- SSH key pair generator
+
+#### 10. Import/Export
+- Encrypted export format
+- Import from 1Password, LastPass, etc.
+- Bulk operations
+
+#### 11. Browser Extension
+- Auto-fill secrets in browser
+- Capture new secrets automatically
+- Secure communication with main app
+
+#### 12. CLI Tool
+- Manage secrets via command line
+- Integration with CI/CD pipelines
+- Environment variable injection
+
+#### 13. Mobile App
+- iOS/Android apps
+- Biometric authentication
+- Offline access (encrypted)
+
+#### 14. Advanced Audit
+- Anomaly detection (unusual access patterns)
+- Real-time alerts
+- Compliance reports (SOC 2, HIPAA)
+
+#### 15. Secret Dependencies
+- Link secrets to connections/resources
+- Cascade delete warnings
+- Dependency graph
+
+---
+
+## Appendix
+
+### Glossary
+
+- **Secret**: Sensitive information (API key, password, token, etc.) stored encrypted
+- **Master Password**: Optional additional password layer for encrypting specific secrets
+- **PRIVATE_KEY**: Application-wide encryption key from environment variable
+- **Share**: Granting access to a secret to another user
+- **Owner**: User who created the secret
+- **Audit Log**: Record of all access and modifications to a secret
+- **Company Secret**: Secret accessible to all users in a company
+
+### References
+
+- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
+- [NIST Password Guidelines](https://pages.nist.gov/800-63-3/)
+- [TypeORM Encryption](https://typeorm.io/)
+- [NestJS Security Best Practices](https://docs.nestjs.com/security/encryption-and-hashing)
+- [Angular Security Guide](https://angular.io/guide/security)
+
+### Environment Variables Required
+
+```bash
+# Existing (required for encryption)
+PRIVATE_KEY=your-64-character-or-longer-encryption-key
+
+# New (optional)
+SECRETS_FEATURE_ENABLED=true  # Feature flag
+SECRET_EXPIRATION_CHECK_CRON="0 0 * * *"  # Daily at midnight
+```
+
+### Configuration Examples
+
+#### TypeORM Entity Registration
+```typescript
+// backend/src/app.module.ts
+TypeOrmModule.forRoot({
+  // ...
+  entities: [
+    // Existing entities...
+    UserSecretEntity,
+    SharedSecretEntity,
+    SecretAccessLogEntity,
+  ],
+}),
+```
+
+#### Angular Lazy Loading
+```typescript
+// frontend/src/app/app-routing.module.ts
+{
+  path: 'secrets',
+  loadChildren: () =>
+    import('./components/secrets/secrets.module').then(m => m.SecretsModule),
+  canActivate: [AuthGuard],
+  data: { title: 'Secrets' },
+}
+```
+
+---
+
+**End of Specification**
+
+**Next Steps**:
+1. Review and approve specification
+2. Set up project board with tasks
+3. Assign developers to phases
+4. Begin Phase 1 implementation
+
+**Questions or Clarifications**:
+- Contact: [Your Team]
+- Slack: #secrets-feature
+- Epic: JIRA-XXX
diff --git a/backend/Dockerfile b/backend/Dockerfile
deleted file mode 100644
index 94d9a4b5..00000000
--- a/backend/Dockerfile
+++ /dev/null
@@ -1,33 +0,0 @@
-FROM public.ecr.aws/docker/library/node:18
-RUN apt-get update && apt-get install -y \
-    tini \
- && rm -rf /var/lib/apt/lists/*
-WORKDIR /app
-COPY package.json yarn.lock nest-cli.json /app/
-RUN yarn install --network-timeout 1000000
-COPY . /app
-RUN yarn run nest build
-CMD [ "tini", "node", "--", "--enable-source-maps", "dist/main.js"]
-ENTRYPOINT ["/app/entrypoint.sh"]
-
-RUN apt update && \
-    apt install libaio1 libc6 curl -y && \
-    cd /tmp && \
-    curl -o instantclient-basiclite.zip https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip -sl && \
-    unzip instantclient-basiclite.zip && \
-    mv instantclient*/ /usr/lib/instantclient && \
-    rm instantclient-basiclite.zip && \
-    ln -s /usr/lib/instantclient/libclntsh.so.19.1 /usr/lib/libclntsh.so && \
-    ln -s /usr/lib/instantclient/libocci.so.19.1 /usr/lib/libocci.so && \
-    ln -s /usr/lib/instantclient/libociicus.so /usr/lib/libociicus.so && \
-    ln -s /usr/lib/instantclient/libnnz19.so /usr/lib/libnnz19.so && \
-    ln -s /usr/lib/libnsl.so.2 /usr/lib/libnsl.so.1 && \
-    ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2 && \
-    ln -s /lib64/ld-linux-x86-64.so.2 /usr/lib/ld-linux-x86-64.so.2
-
-ENV ORACLE_BASE /usr/lib/instantclient
-ENV LD_LIBRARY_PATH /usr/lib/instantclient
-ENV TNS_ADMIN /usr/lib/instantclient
-ENV ORACLE_HOME /usr/lib/instantclient
-ENV TYPEORM_CONNECTION postgres
-ENV TYPEORM_MIGRATIONS dist/src/migrations/*.js
diff --git a/backend/ava.config.mjs b/backend/ava.config.mjs
index e36a982f..ed404e49 100644
--- a/backend/ava.config.mjs
+++ b/backend/ava.config.mjs
@@ -8,7 +8,7 @@ export default {
       'src/': 'dist/src/',
       'test/': 'dist/test/',
     },
-    compile: 'tsc',
+    compile: false,
   },
   workerThreads: false,
   verbose: true,
diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts
index 473f9d8e..5f8ca63e 100644
--- a/backend/src/app.module.ts
+++ b/backend/src/app.module.ts
@@ -39,6 +39,7 @@ import { GetHelloUseCase } from './use-cases-app/get-hello.use.case.js';
 import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
 import { SharedJobsModule } from './entities/shared-jobs/shared-jobs.module.js';
 import { TableCategoriesModule } from './entities/table-categories/table-categories.module.js';
+import { UserSecretModule } from './entities/user-secret/user-secret.module.js';
 import { SignInAuditModule } from './entities/user-sign-in-audit/sign-in-audit.module.js';
 
 @Module({
@@ -82,6 +83,7 @@ import { SignInAuditModule } from './entities/user-sign-in-audit/sign-in-audit.m
     LoggingModule,
     SharedJobsModule,
     TableCategoriesModule,
+    UserSecretModule,
     SignInAuditModule,
   ],
   controllers: [AppController],
diff --git a/backend/src/common/application/global-database-context.interface.ts b/backend/src/common/application/global-database-context.interface.ts
index 26ab97e7..dc354ee6 100644
--- a/backend/src/common/application/global-database-context.interface.ts
+++ b/backend/src/common/application/global-database-context.interface.ts
@@ -48,6 +48,10 @@ import { IDatabaseContext } from '../database-context.interface.js';
 import { TableCategoriesEntity } from '../../entities/table-categories/table-categories.entity.js';
 import { ITableCategoriesCustomRepository } from '../../entities/table-categories/repository/table-categories-repository.interface.js';
 import { ConnectionPropertiesEntity } from '../../entities/connection-properties/connection-properties.entity.js';
+import { UserSecretEntity } from '../../entities/user-secret/user-secret.entity.js';
+import { IUserSecretRepository } from '../../entities/user-secret/repository/user-secret-repository.interface.js';
+import { SecretAccessLogEntity } from '../../entities/secret-access-log/secret-access-log.entity.js';
+import { ISecretAccessLogRepository } from '../../entities/secret-access-log/repository/secret-access-log-repository.interface.js';
 import { SignInAuditEntity } from '../../entities/user-sign-in-audit/sign-in-audit.entity.js';
 import { ISignInAuditRepository } from '../../entities/user-sign-in-audit/repository/sign-in-audit-repository.interface.js';
 
@@ -85,5 +89,7 @@ export interface IGlobalDatabaseContext extends IDatabaseContext {
   tableFiltersRepository: Repository & ITableFiltersCustomRepository;
   aiResponsesToUserRepository: Repository & IAiResponsesToUserRepository;
   tableCategoriesRepository: Repository & ITableCategoriesCustomRepository;
+  userSecretRepository: Repository & IUserSecretRepository;
+  secretAccessLogRepository: Repository & ISecretAccessLogRepository;
   signInAuditRepository: Repository & ISignInAuditRepository;
 }
diff --git a/backend/src/common/application/global-database-context.ts b/backend/src/common/application/global-database-context.ts
index 077d7cc1..5e202076 100644
--- a/backend/src/common/application/global-database-context.ts
+++ b/backend/src/common/application/global-database-context.ts
@@ -90,6 +90,12 @@ import { aiResponsesToUserRepositoryExtension } from '../../entities/ai/ai-data-
 import { TableCategoriesEntity } from '../../entities/table-categories/table-categories.entity.js';
 import { ITableCategoriesCustomRepository } from '../../entities/table-categories/repository/table-categories-repository.interface.js';
 import { tableCategoriesCustomRepositoryExtension } from '../../entities/table-categories/repository/table-categories-repository.extension.js';
+import { UserSecretEntity } from '../../entities/user-secret/user-secret.entity.js';
+import { IUserSecretRepository } from '../../entities/user-secret/repository/user-secret-repository.interface.js';
+import { userSecretRepositoryExtension } from '../../entities/user-secret/repository/user-secret-repository.extension.js';
+import { SecretAccessLogEntity } from '../../entities/secret-access-log/secret-access-log.entity.js';
+import { ISecretAccessLogRepository } from '../../entities/secret-access-log/repository/secret-access-log-repository.interface.js';
+import { secretAccessLogRepositoryExtension } from '../../entities/secret-access-log/repository/secret-access-log-repository.extension.js';
 import { SignInAuditEntity } from '../../entities/user-sign-in-audit/sign-in-audit.entity.js';
 import { ISignInAuditRepository } from '../../entities/user-sign-in-audit/repository/sign-in-audit-repository.interface.js';
 import { signInAuditCustomRepositoryExtension } from '../../entities/user-sign-in-audit/repository/sign-in-audit-custom-repository-extension.js';
@@ -131,6 +137,8 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
   private _tableFiltersRepository: Repository & ITableFiltersCustomRepository;
   private _aiResponsesToUserRepository: Repository & IAiResponsesToUserRepository;
   private _tableCategoriesRepository: Repository & ITableCategoriesCustomRepository;
+  private _userSecretRepository: Repository & IUserSecretRepository;
+  private _secretAccessLogRepository: Repository & ISecretAccessLogRepository;
   private _signInAuditRepository: Repository & ISignInAuditRepository;
 
   public constructor(
@@ -220,6 +228,12 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
     this._tableCategoriesRepository = this.appDataSource
       .getRepository(TableCategoriesEntity)
       .extend(tableCategoriesCustomRepositoryExtension);
+    this._userSecretRepository = this.appDataSource
+      .getRepository(UserSecretEntity)
+      .extend(userSecretRepositoryExtension);
+    this._secretAccessLogRepository = this.appDataSource
+      .getRepository(SecretAccessLogEntity)
+      .extend(secretAccessLogRepositoryExtension);
     this._signInAuditRepository = this.appDataSource
       .getRepository(SignInAuditEntity)
       .extend(signInAuditCustomRepositoryExtension);
@@ -357,6 +371,14 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
     return this._tableCategoriesRepository;
   }
 
+  public get userSecretRepository(): Repository & IUserSecretRepository {
+    return this._userSecretRepository;
+  }
+
+  public get secretAccessLogRepository(): Repository & ISecretAccessLogRepository {
+    return this._secretAccessLogRepository;
+  }
+
   public get signInAuditRepository(): Repository & ISignInAuditRepository {
     return this._signInAuditRepository;
   }
diff --git a/backend/src/common/data-injection.tokens.ts b/backend/src/common/data-injection.tokens.ts
index 353e8466..bb0176b5 100644
--- a/backend/src/common/data-injection.tokens.ts
+++ b/backend/src/common/data-injection.tokens.ts
@@ -165,5 +165,11 @@ export enum UseCaseType {
   CREATE_UPDATE_TABLE_CATEGORIES = 'CREATE_UPDATE_TABLE_CATEGORIES',
   FIND_TABLE_CATEGORIES = 'FIND_TABLE_CATEGORIES',
 
+  CREATE_SECRET = 'CREATE_SECRET',
+  GET_SECRETS = 'GET_SECRETS',
+  GET_SECRET_BY_SLUG = 'GET_SECRET_BY_SLUG',
+  UPDATE_SECRET = 'UPDATE_SECRET',
+  DELETE_SECRET = 'DELETE_SECRET',
+  GET_SECRET_AUDIT_LOG = 'GET_SECRET_AUDIT_LOG',
   FIND_SIGN_IN_AUDIT_LOGS = 'FIND_SIGN_IN_AUDIT_LOGS',
 }
diff --git a/backend/src/entities/company-info/company-info.entity.ts b/backend/src/entities/company-info/company-info.entity.ts
index 945cde5d..44516b69 100644
--- a/backend/src/entities/company-info/company-info.entity.ts
+++ b/backend/src/entities/company-info/company-info.entity.ts
@@ -6,6 +6,7 @@ import { generateCompanyName } from './utils/get-company-name.js';
 import { CompanyLogoEntity } from '../company-logo/company-logo.entity.js';
 import { CompanyFaviconEntity } from '../company-favicon/company-favicon.entity.js';
 import { CompanyTabTitleEntity } from '../company-tab-title/company-tab-title.entity.js';
+import { UserSecretEntity } from '../user-secret/user-secret.entity.js';
 
 @Entity('company_info')
 export class CompanyInfoEntity {
@@ -60,4 +61,9 @@ export class CompanyInfoEntity {
     onDelete: 'NO ACTION',
   })
   tab_title: Relation;
+
+  @OneToMany((_) => UserSecretEntity, (secret) => secret.company, {
+    onDelete: 'NO ACTION',
+  })
+  secrets: Relation[];
 }
diff --git a/backend/src/entities/secret-access-log/repository/secret-access-log-repository.extension.ts b/backend/src/entities/secret-access-log/repository/secret-access-log-repository.extension.ts
new file mode 100644
index 00000000..2e6d84c0
--- /dev/null
+++ b/backend/src/entities/secret-access-log/repository/secret-access-log-repository.extension.ts
@@ -0,0 +1,39 @@
+import { SecretAccessLogEntity, SecretActionEnum } from '../secret-access-log.entity.js';
+import { ISecretAccessLogRepository } from './secret-access-log-repository.interface.js';
+
+export const secretAccessLogRepositoryExtension: ISecretAccessLogRepository = {
+  async createAccessLog(
+    secretId: string,
+    userId: string,
+    action: SecretActionEnum,
+    success: boolean = true,
+    errorMessage?: string,
+  ): Promise {
+    const log = this.create({
+      secretId,
+      userId,
+      action,
+      success,
+      errorMessage,
+      accessedAt: new Date(),
+    });
+    return this.save(log);
+  },
+
+  async findLogsForSecret(
+    secretId: string,
+    options: { page: number; limit: number },
+  ): Promise<[SecretAccessLogEntity[], number]> {
+    return this.findAndCount({
+      where: {
+        secretId,
+      },
+      relations: ['user'],
+      order: {
+        accessedAt: 'DESC',
+      },
+      skip: (options.page - 1) * options.limit,
+      take: options.limit,
+    });
+  },
+};
diff --git a/backend/src/entities/secret-access-log/repository/secret-access-log-repository.interface.ts b/backend/src/entities/secret-access-log/repository/secret-access-log-repository.interface.ts
new file mode 100644
index 00000000..374bab0d
--- /dev/null
+++ b/backend/src/entities/secret-access-log/repository/secret-access-log-repository.interface.ts
@@ -0,0 +1,18 @@
+import { Repository } from 'typeorm';
+import { SecretAccessLogEntity, SecretActionEnum } from '../secret-access-log.entity.js';
+
+export interface ISecretAccessLogRepository {
+  createAccessLog(
+    this: Repository,
+    secretId: string,
+    userId: string,
+    action: SecretActionEnum,
+    success?: boolean,
+    errorMessage?: string,
+  ): Promise;
+  findLogsForSecret(
+    this: Repository,
+    secretId: string,
+    options: { page: number; limit: number },
+  ): Promise<[SecretAccessLogEntity[], number]>;
+}
diff --git a/backend/src/entities/secret-access-log/secret-access-log.entity.ts b/backend/src/entities/secret-access-log/secret-access-log.entity.ts
new file mode 100644
index 00000000..9e1cbd40
--- /dev/null
+++ b/backend/src/entities/secret-access-log/secret-access-log.entity.ts
@@ -0,0 +1,55 @@
+import { Column, CreateDateColumn, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn, Relation } from 'typeorm';
+import { UserEntity } from '../user/user.entity.js';
+import { UserSecretEntity } from '../user-secret/user-secret.entity.js';
+
+export enum SecretActionEnum {
+  CREATE = 'create',
+  VIEW = 'view',
+  COPY = 'copy',
+  UPDATE = 'update',
+  DELETE = 'delete',
+}
+
+@Entity('secret_access_logs')
+export class SecretAccessLogEntity {
+  @PrimaryGeneratedColumn('uuid')
+  id: string;
+
+  @ManyToOne(() => UserSecretEntity, (secret) => secret.accessLogs, { onDelete: 'CASCADE' })
+  @JoinColumn({ name: 'secretId' })
+  secret: Relation;
+
+  @Column()
+  @Index()
+  secretId: string;
+
+  @ManyToOne(() => UserEntity, (user) => user.secretAccessLogs, { onDelete: 'CASCADE' })
+  @JoinColumn({ name: 'userId' })
+  user: Relation;
+
+  @Column()
+  @Index()
+  userId: string;
+
+  @Column({
+    type: 'enum',
+    enum: SecretActionEnum,
+  })
+  action: SecretActionEnum;
+
+  @CreateDateColumn()
+  @Index()
+  accessedAt: Date;
+
+  @Column({ type: 'varchar', length: 45, nullable: true })
+  ipAddress: string;
+
+  @Column({ type: 'text', nullable: true })
+  userAgent: string;
+
+  @Column({ default: true })
+  success: boolean;
+
+  @Column({ type: 'text', nullable: true })
+  errorMessage: string;
+}
diff --git a/backend/src/entities/user-secret/application/data-structures/create-secret.ds.ts b/backend/src/entities/user-secret/application/data-structures/create-secret.ds.ts
new file mode 100644
index 00000000..a4fd71ab
--- /dev/null
+++ b/backend/src/entities/user-secret/application/data-structures/create-secret.ds.ts
@@ -0,0 +1,8 @@
+export class CreateSecretDS {
+  userId: string;
+  slug: string;
+  value: string;
+  expiresAt?: string;
+  masterEncryption?: boolean;
+  masterPassword?: string;
+}
diff --git a/backend/src/entities/user-secret/application/data-structures/created-secret.ds.ts b/backend/src/entities/user-secret/application/data-structures/created-secret.ds.ts
new file mode 100644
index 00000000..b1b98bc8
--- /dev/null
+++ b/backend/src/entities/user-secret/application/data-structures/created-secret.ds.ts
@@ -0,0 +1,10 @@
+export class CreatedSecretDS {
+  id: string;
+  slug: string;
+  companyId: string;
+  createdAt: Date;
+  updatedAt: Date;
+  lastAccessedAt?: Date;
+  expiresAt?: Date;
+  masterEncryption: boolean;
+}
diff --git a/backend/src/entities/user-secret/application/data-structures/delete-secret.ds.ts b/backend/src/entities/user-secret/application/data-structures/delete-secret.ds.ts
new file mode 100644
index 00000000..1de6f1b2
--- /dev/null
+++ b/backend/src/entities/user-secret/application/data-structures/delete-secret.ds.ts
@@ -0,0 +1,9 @@
+export class DeleteSecretDS {
+  userId: string;
+  slug: string;
+}
+
+export class DeletedSecretDS {
+  message: string;
+  deletedAt: Date;
+}
diff --git a/backend/src/entities/user-secret/application/data-structures/found-secret.ds.ts b/backend/src/entities/user-secret/application/data-structures/found-secret.ds.ts
new file mode 100644
index 00000000..8e119df6
--- /dev/null
+++ b/backend/src/entities/user-secret/application/data-structures/found-secret.ds.ts
@@ -0,0 +1,11 @@
+export class FoundSecretDS {
+  id: string;
+  slug: string;
+  value?: string;
+  companyId: string;
+  createdAt: Date;
+  updatedAt: Date;
+  lastAccessedAt?: Date;
+  expiresAt?: Date;
+  masterEncryption: boolean;
+}
diff --git a/backend/src/entities/user-secret/application/data-structures/get-audit-log.ds.ts b/backend/src/entities/user-secret/application/data-structures/get-audit-log.ds.ts
new file mode 100644
index 00000000..e329a5c4
--- /dev/null
+++ b/backend/src/entities/user-secret/application/data-structures/get-audit-log.ds.ts
@@ -0,0 +1,28 @@
+import { SecretActionEnum } from '../../../secret-access-log/secret-access-log.entity.js';
+import { PaginationDs } from '../../../table/application/data-structures/pagination.ds.js';
+
+export class GetAuditLogDS {
+  userId: string;
+  slug: string;
+  page: number;
+  limit: number;
+}
+
+export class AuditLogEntryDS {
+  id: string;
+  action: SecretActionEnum;
+  user: {
+    id: string;
+    email: string;
+  };
+  accessedAt: Date;
+  ipAddress?: string;
+  userAgent?: string;
+  success: boolean;
+  errorMessage?: string;
+}
+
+export class AuditLogListDS {
+  data: AuditLogEntryDS[];
+  pagination: PaginationDs;
+}
diff --git a/backend/src/entities/user-secret/application/data-structures/get-secret.ds.ts b/backend/src/entities/user-secret/application/data-structures/get-secret.ds.ts
new file mode 100644
index 00000000..9ad4fcd6
--- /dev/null
+++ b/backend/src/entities/user-secret/application/data-structures/get-secret.ds.ts
@@ -0,0 +1,5 @@
+export class GetSecretDS {
+  userId: string;
+  slug: string;
+  masterPassword?: string;
+}
diff --git a/backend/src/entities/user-secret/application/data-structures/get-secrets.ds.ts b/backend/src/entities/user-secret/application/data-structures/get-secrets.ds.ts
new file mode 100644
index 00000000..8b68bf4a
--- /dev/null
+++ b/backend/src/entities/user-secret/application/data-structures/get-secrets.ds.ts
@@ -0,0 +1,24 @@
+import { PaginationDs } from '../../../table/application/data-structures/pagination.ds.js';
+
+export class GetSecretsDS {
+  userId: string;
+  page: number;
+  limit: number;
+  search?: string;
+}
+
+export class SecretListItemDS {
+  id: string;
+  slug: string;
+  companyId: string;
+  createdAt: Date;
+  updatedAt: Date;
+  lastAccessedAt?: Date;
+  expiresAt?: Date;
+  masterEncryption: boolean;
+}
+
+export class SecretsListDS {
+  data: SecretListItemDS[];
+  pagination: PaginationDs;
+}
diff --git a/backend/src/entities/user-secret/application/data-structures/update-secret.ds.ts b/backend/src/entities/user-secret/application/data-structures/update-secret.ds.ts
new file mode 100644
index 00000000..cefcd159
--- /dev/null
+++ b/backend/src/entities/user-secret/application/data-structures/update-secret.ds.ts
@@ -0,0 +1,18 @@
+export class UpdateSecretDS {
+  userId: string;
+  slug: string;
+  value?: string;
+  expiresAt?: string;
+  masterPassword?: string;
+}
+
+export class UpdatedSecretDS {
+  id: string;
+  slug: string;
+  companyId: string;
+  createdAt: Date;
+  updatedAt: Date;
+  lastAccessedAt?: Date;
+  expiresAt?: Date;
+  masterEncryption: boolean;
+}
diff --git a/backend/src/entities/user-secret/application/dto/audit-log.dto.ts b/backend/src/entities/user-secret/application/dto/audit-log.dto.ts
new file mode 100644
index 00000000..5585490b
--- /dev/null
+++ b/backend/src/entities/user-secret/application/dto/audit-log.dto.ts
@@ -0,0 +1,95 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { SecretActionEnum } from '../../../secret-access-log/secret-access-log.entity.js';
+import { PaginationDs } from '../../../table/application/data-structures/pagination.ds.js';
+
+export class AuditLogUserDto {
+  @ApiProperty({
+    type: String,
+    description: 'User ID who performed the action',
+    example: '550e8400-e29b-41d4-a716-446655440002',
+  })
+  id: string;
+
+  @ApiProperty({
+    type: String,
+    description: 'Email of the user who performed the action',
+    example: 'user@example.com',
+  })
+  email: string;
+}
+
+export class AuditLogEntryDto {
+  @ApiProperty({
+    type: String,
+    description: 'Unique identifier of the audit log entry',
+    example: '550e8400-e29b-41d4-a716-446655440003',
+  })
+  id: string;
+
+  @ApiProperty({
+    enum: SecretActionEnum,
+    enumName: 'SecretActionEnum',
+    description: 'Type of action performed on the secret',
+    example: SecretActionEnum.VIEW,
+  })
+  action: SecretActionEnum;
+
+  @ApiProperty({
+    type: AuditLogUserDto,
+    description: 'User who performed the action',
+  })
+  user: AuditLogUserDto;
+
+  @ApiProperty({
+    type: Date,
+    description: 'Date and time when the action was performed',
+    example: '2025-01-25T09:15:00.000Z',
+  })
+  accessedAt: Date;
+
+  @ApiProperty({
+    type: String,
+    required: false,
+    description: 'IP address from which the action was performed',
+    example: '192.168.1.100',
+  })
+  ipAddress?: string;
+
+  @ApiProperty({
+    type: String,
+    required: false,
+    description: 'User agent string of the client',
+    example: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0',
+  })
+  userAgent?: string;
+
+  @ApiProperty({
+    type: Boolean,
+    description: 'Whether the action was successful',
+    example: true,
+  })
+  success: boolean;
+
+  @ApiProperty({
+    type: String,
+    required: false,
+    description: 'Error message if the action failed',
+    example: 'Invalid master password',
+  })
+  errorMessage?: string;
+}
+
+export class AuditLogResponseDto {
+  @ApiProperty({
+    type: AuditLogEntryDto,
+    isArray: true,
+    description: 'List of audit log entries',
+  })
+  data: AuditLogEntryDto[];
+
+  @ApiProperty({
+    type: PaginationDs,
+    description: 'Pagination information',
+  })
+  pagination: PaginationDs;
+}
diff --git a/backend/src/entities/user-secret/application/dto/create-secret.dto.ts b/backend/src/entities/user-secret/application/dto/create-secret.dto.ts
new file mode 100644
index 00000000..d97c383f
--- /dev/null
+++ b/backend/src/entities/user-secret/application/dto/create-secret.dto.ts
@@ -0,0 +1,71 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { Transform } from 'class-transformer';
+import { IsBoolean, IsISO8601, IsNotEmpty, IsOptional, IsString, Matches, MaxLength, MinLength, ValidateIf } from 'class-validator';
+
+export class CreateSecretDto {
+  @ApiProperty({
+    type: String,
+    required: true,
+    description: 'Unique identifier for the secret within the company',
+    example: 'database-password',
+    minLength: 1,
+    maxLength: 255,
+    pattern: '^[a-zA-Z0-9_-]+$',
+  })
+  @IsString()
+  @IsNotEmpty()
+  @MinLength(1)
+  @MaxLength(255)
+  @Matches(/^[a-zA-Z0-9_-]+$/, {
+    message: 'slug must contain only letters, numbers, hyphens, and underscores',
+  })
+  @Transform(({ value }) => value.trim())
+  slug: string;
+
+  @ApiProperty({
+    type: String,
+    required: true,
+    description: 'The secret value to be stored (will be encrypted)',
+    example: 'my-secret-value-123',
+    minLength: 1,
+    maxLength: 10000,
+  })
+  @IsString()
+  @IsNotEmpty()
+  @MinLength(1)
+  @MaxLength(10000)
+  value: string;
+
+  @ApiProperty({
+    type: String,
+    required: false,
+    description: 'ISO 8601 date when the secret should expire',
+    example: '2025-12-31T23:59:59.000Z',
+  })
+  @IsOptional()
+  @IsISO8601()
+  expiresAt?: string;
+
+  @ApiProperty({
+    type: Boolean,
+    required: false,
+    default: false,
+    description: 'Whether to encrypt the secret with a master password',
+    example: false,
+  })
+  @IsBoolean()
+  @IsOptional()
+  masterEncryption?: boolean;
+
+  @ApiProperty({
+    type: String,
+    required: false,
+    description: 'Master password for additional encryption (required if masterEncryption is true)',
+    example: 'master-password-123',
+    minLength: 8,
+  })
+  @IsString()
+  @MinLength(8)
+  @ValidateIf((o) => o.masterEncryption === true)
+  masterPassword?: string;
+}
diff --git a/backend/src/entities/user-secret/application/dto/delete-secret.dto.ts b/backend/src/entities/user-secret/application/dto/delete-secret.dto.ts
new file mode 100644
index 00000000..de98cd93
--- /dev/null
+++ b/backend/src/entities/user-secret/application/dto/delete-secret.dto.ts
@@ -0,0 +1,17 @@
+import { ApiProperty } from '@nestjs/swagger';
+
+export class DeleteSecretResponseDto {
+  @ApiProperty({
+    type: String,
+    description: 'Confirmation message',
+    example: 'Secret deleted successfully',
+  })
+  message: string;
+
+  @ApiProperty({
+    type: Date,
+    description: 'Date and time when the secret was deleted',
+    example: '2025-01-25T10:30:00.000Z',
+  })
+  deletedAt: Date;
+}
diff --git a/backend/src/entities/user-secret/application/dto/found-secret.dto.ts b/backend/src/entities/user-secret/application/dto/found-secret.dto.ts
new file mode 100644
index 00000000..65d32668
--- /dev/null
+++ b/backend/src/entities/user-secret/application/dto/found-secret.dto.ts
@@ -0,0 +1,69 @@
+import { ApiProperty } from '@nestjs/swagger';
+
+export class FoundSecretDto {
+  @ApiProperty({
+    type: String,
+    description: 'Unique identifier of the secret',
+    example: '550e8400-e29b-41d4-a716-446655440000',
+  })
+  id: string;
+
+  @ApiProperty({
+    type: String,
+    description: 'Unique slug identifier for the secret',
+    example: 'database-password',
+  })
+  slug: string;
+
+  @ApiProperty({
+    type: String,
+    required: false,
+    description: 'Decrypted secret value (only included when retrieving a specific secret)',
+    example: 'my-secret-value-123',
+  })
+  value?: string;
+
+  @ApiProperty({
+    type: String,
+    description: 'Company ID that owns this secret',
+    example: '550e8400-e29b-41d4-a716-446655440001',
+  })
+  companyId: string;
+
+  @ApiProperty({
+    type: Date,
+    description: 'Date when the secret was created',
+    example: '2025-01-15T10:30:00.000Z',
+  })
+  createdAt: Date;
+
+  @ApiProperty({
+    type: Date,
+    description: 'Date when the secret was last updated',
+    example: '2025-01-20T14:45:00.000Z',
+  })
+  updatedAt: Date;
+
+  @ApiProperty({
+    type: Date,
+    required: false,
+    description: 'Date when the secret was last accessed',
+    example: '2025-01-25T09:15:00.000Z',
+  })
+  lastAccessedAt?: Date;
+
+  @ApiProperty({
+    type: Date,
+    required: false,
+    description: 'Date when the secret expires (null if no expiration)',
+    example: '2025-12-31T23:59:59.000Z',
+  })
+  expiresAt?: Date;
+
+  @ApiProperty({
+    type: Boolean,
+    description: 'Whether the secret requires a master password for decryption',
+    example: false,
+  })
+  masterEncryption: boolean;
+}
diff --git a/backend/src/entities/user-secret/application/dto/secret-list.dto.ts b/backend/src/entities/user-secret/application/dto/secret-list.dto.ts
new file mode 100644
index 00000000..a768c69f
--- /dev/null
+++ b/backend/src/entities/user-secret/application/dto/secret-list.dto.ts
@@ -0,0 +1,77 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { PaginationDs } from '../../../table/application/data-structures/pagination.ds.js';
+
+export class SecretListItemDto {
+  @ApiProperty({
+    type: String,
+    description: 'Unique identifier of the secret',
+    example: '550e8400-e29b-41d4-a716-446655440000',
+  })
+  id: string;
+
+  @ApiProperty({
+    type: String,
+    description: 'Unique slug identifier for the secret',
+    example: 'database-password',
+  })
+  slug: string;
+
+  @ApiProperty({
+    type: String,
+    description: 'Company ID that owns this secret',
+    example: '550e8400-e29b-41d4-a716-446655440001',
+  })
+  companyId: string;
+
+  @ApiProperty({
+    type: Date,
+    description: 'Date when the secret was created',
+    example: '2025-01-15T10:30:00.000Z',
+  })
+  createdAt: Date;
+
+  @ApiProperty({
+    type: Date,
+    description: 'Date when the secret was last updated',
+    example: '2025-01-20T14:45:00.000Z',
+  })
+  updatedAt: Date;
+
+  @ApiProperty({
+    type: Date,
+    required: false,
+    description: 'Date when the secret was last accessed',
+    example: '2025-01-25T09:15:00.000Z',
+  })
+  lastAccessedAt?: Date;
+
+  @ApiProperty({
+    type: Date,
+    required: false,
+    description: 'Date when the secret expires (null if no expiration)',
+    example: '2025-12-31T23:59:59.000Z',
+  })
+  expiresAt?: Date;
+
+  @ApiProperty({
+    type: Boolean,
+    description: 'Whether the secret requires a master password for decryption',
+    example: false,
+  })
+  masterEncryption: boolean;
+}
+
+export class SecretListResponseDto {
+  @ApiProperty({
+    type: SecretListItemDto,
+    isArray: true,
+    description: 'List of secrets',
+  })
+  data: SecretListItemDto[];
+
+  @ApiProperty({
+    type: PaginationDs,
+    description: 'Pagination information',
+  })
+  pagination: PaginationDs;
+}
diff --git a/backend/src/entities/user-secret/application/dto/update-secret.dto.ts b/backend/src/entities/user-secret/application/dto/update-secret.dto.ts
new file mode 100644
index 00000000..4037bc01
--- /dev/null
+++ b/backend/src/entities/user-secret/application/dto/update-secret.dto.ts
@@ -0,0 +1,28 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { IsISO8601, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator';
+
+export class UpdateSecretDto {
+  @ApiProperty({
+    type: String,
+    required: true,
+    description: 'The new secret value (will be encrypted)',
+    example: 'my-updated-secret-value-456',
+    minLength: 1,
+    maxLength: 10000,
+  })
+  @IsString()
+  @IsNotEmpty()
+  @MinLength(1)
+  @MaxLength(10000)
+  value: string;
+
+  @ApiProperty({
+    type: String,
+    required: false,
+    description: 'ISO 8601 date when the secret should expire (null to remove expiration)',
+    example: '2025-12-31T23:59:59.000Z',
+  })
+  @IsOptional()
+  @IsISO8601()
+  expiresAt?: string;
+}
diff --git a/backend/src/entities/user-secret/repository/user-secret-repository.extension.ts b/backend/src/entities/user-secret/repository/user-secret-repository.extension.ts
new file mode 100644
index 00000000..9ac42f78
--- /dev/null
+++ b/backend/src/entities/user-secret/repository/user-secret-repository.extension.ts
@@ -0,0 +1,36 @@
+import { FindOptionsWhere, Like } from 'typeorm';
+import { UserSecretEntity } from '../user-secret.entity.js';
+import { IUserSecretRepository } from './user-secret-repository.interface.js';
+
+export const userSecretRepositoryExtension: IUserSecretRepository = {
+  async findSecretBySlugAndCompanyId(slug: string, companyId: string): Promise {
+    return this.findOne({
+      where: {
+        slug,
+        companyId,
+      },
+    });
+  },
+
+  async findSecretsForCompany(
+    companyId: string,
+    options: { page: number; limit: number; search?: string },
+  ): Promise<[UserSecretEntity[], number]> {
+    const where: FindOptionsWhere = {
+      companyId,
+    };
+
+    if (options.search) {
+      where.slug = Like(`%${options.search}%`);
+    }
+
+    return this.findAndCount({
+      where,
+      order: {
+        createdAt: 'DESC',
+      },
+      skip: (options.page - 1) * options.limit,
+      take: options.limit,
+    });
+  },
+};
diff --git a/backend/src/entities/user-secret/repository/user-secret-repository.interface.ts b/backend/src/entities/user-secret/repository/user-secret-repository.interface.ts
new file mode 100644
index 00000000..8040435c
--- /dev/null
+++ b/backend/src/entities/user-secret/repository/user-secret-repository.interface.ts
@@ -0,0 +1,15 @@
+import { Repository } from 'typeorm';
+import { UserSecretEntity } from '../user-secret.entity.js';
+
+export interface IUserSecretRepository {
+  findSecretBySlugAndCompanyId(
+    this: Repository,
+    slug: string,
+    companyId: string,
+  ): Promise;
+  findSecretsForCompany(
+    this: Repository,
+    companyId: string,
+    options: { page: number; limit: number; search?: string },
+  ): Promise<[UserSecretEntity[], number]>;
+}
diff --git a/backend/src/entities/user-secret/use-cases/create-secret.use.case.ts b/backend/src/entities/user-secret/use-cases/create-secret.use.case.ts
new file mode 100644
index 00000000..793ea051
--- /dev/null
+++ b/backend/src/entities/user-secret/use-cases/create-secret.use.case.ts
@@ -0,0 +1,65 @@
+import { ConflictException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
+import AbstractUseCase from '../../../common/abstract-use.case.js';
+import { BaseType } from '../../../common/data-injection.tokens.js';
+import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
+import { CreateSecretDS } from '../application/data-structures/create-secret.ds.js';
+import { CreatedSecretDS } from '../application/data-structures/created-secret.ds.js';
+import { ICreateSecret } from './user-secret-use-cases.interface.js';
+import { buildCreatedSecretDS } from '../utils/build-created-secret.ds.js';
+import { Encryptor } from '../../../helpers/encryption/encryptor.js';
+import { SecretActionEnum } from '../../secret-access-log/secret-access-log.entity.js';
+import { Messages } from '../../../exceptions/text/messages.js';
+
+@Injectable({ scope: Scope.REQUEST })
+export class CreateSecretUseCase extends AbstractUseCase implements ICreateSecret {
+  constructor(
+    @Inject(BaseType.GLOBAL_DB_CONTEXT)
+    protected _dbContext: IGlobalDatabaseContext,
+  ) {
+    super();
+  }
+
+  protected async implementation(inputData: CreateSecretDS): Promise {
+    const { userId, slug, value, expiresAt, masterEncryption, masterPassword } = inputData;
+
+    const user = await this._dbContext.userRepository.findOne({
+      where: { id: userId },
+      relations: ['company'],
+    });
+
+    if (!user || !user.company) {
+      throw new NotFoundException(Messages.USER_NOT_FOUND_OR_NOT_IN_COMPANY);
+    }
+
+    const existing = await this._dbContext.userSecretRepository.findSecretBySlugAndCompanyId(slug, user.company.id);
+
+    if (existing) {
+      throw new ConflictException(Messages.SECRET_ALREADY_EXISTS);
+    }
+
+    let encryptedValue = value;
+
+    if (masterEncryption && masterPassword) {
+      encryptedValue = Encryptor.encryptDataMasterPwd(encryptedValue, masterPassword);
+    }
+
+    encryptedValue = Encryptor.encryptData(encryptedValue);
+
+    const masterHash = masterPassword ? await Encryptor.hashUserPassword(masterPassword) : null;
+
+    const secret = this._dbContext.userSecretRepository.create({
+      slug,
+      encryptedValue,
+      companyId: user.company.id,
+      expiresAt: expiresAt ? new Date(expiresAt) : null,
+      masterEncryption: masterEncryption || false,
+      masterHash,
+    });
+
+    const saved = await this._dbContext.userSecretRepository.save(secret);
+
+    await this._dbContext.secretAccessLogRepository.createAccessLog(saved.id, userId, SecretActionEnum.CREATE);
+
+    return buildCreatedSecretDS(saved);
+  }
+}
diff --git a/backend/src/entities/user-secret/use-cases/delete-secret.use.case.ts b/backend/src/entities/user-secret/use-cases/delete-secret.use.case.ts
new file mode 100644
index 00000000..f740e31e
--- /dev/null
+++ b/backend/src/entities/user-secret/use-cases/delete-secret.use.case.ts
@@ -0,0 +1,46 @@
+import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
+import AbstractUseCase from '../../../common/abstract-use.case.js';
+import { BaseType } from '../../../common/data-injection.tokens.js';
+import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
+import { DeleteSecretDS, DeletedSecretDS } from '../application/data-structures/delete-secret.ds.js';
+import { IDeleteSecret } from './user-secret-use-cases.interface.js';
+import { SecretActionEnum } from '../../secret-access-log/secret-access-log.entity.js';
+import { Messages } from '../../../exceptions/text/messages.js';
+
+@Injectable({ scope: Scope.REQUEST })
+export class DeleteSecretUseCase extends AbstractUseCase implements IDeleteSecret {
+  constructor(
+    @Inject(BaseType.GLOBAL_DB_CONTEXT)
+    protected _dbContext: IGlobalDatabaseContext,
+  ) {
+    super();
+  }
+
+  protected async implementation(inputData: DeleteSecretDS): Promise {
+    const { userId, slug } = inputData;
+
+    const user = await this._dbContext.userRepository.findOne({
+      where: { id: userId },
+      relations: ['company'],
+    });
+
+    if (!user || !user.company) {
+      throw new NotFoundException(Messages.USER_NOT_FOUND_OR_NOT_IN_COMPANY);
+    }
+
+    const secret = await this._dbContext.userSecretRepository.findSecretBySlugAndCompanyId(slug, user.company.id);
+
+    if (!secret) {
+      throw new NotFoundException(Messages.SECRET_NOT_FOUND);
+    }
+
+    await this._dbContext.secretAccessLogRepository.createAccessLog(secret.id, userId, SecretActionEnum.DELETE);
+
+    await this._dbContext.userSecretRepository.remove(secret);
+
+    return {
+      message: Messages.SECRET_DELETED_SUCCESSFULLY,
+      deletedAt: new Date(),
+    };
+  }
+}
diff --git a/backend/src/entities/user-secret/use-cases/get-secret-audit-log.use.case.ts b/backend/src/entities/user-secret/use-cases/get-secret-audit-log.use.case.ts
new file mode 100644
index 00000000..b3b2138e
--- /dev/null
+++ b/backend/src/entities/user-secret/use-cases/get-secret-audit-log.use.case.ts
@@ -0,0 +1,55 @@
+import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
+import AbstractUseCase from '../../../common/abstract-use.case.js';
+import { BaseType } from '../../../common/data-injection.tokens.js';
+import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
+import { AuditLogListDS, GetAuditLogDS } from '../application/data-structures/get-audit-log.ds.js';
+import { IGetSecretAuditLog } from './user-secret-use-cases.interface.js';
+import { buildAuditLogEntryDS } from '../utils/build-audit-log-entry.ds.js';
+import { Messages } from '../../../exceptions/text/messages.js';
+
+@Injectable({ scope: Scope.REQUEST })
+export class GetSecretAuditLogUseCase
+  extends AbstractUseCase
+  implements IGetSecretAuditLog
+{
+  constructor(
+    @Inject(BaseType.GLOBAL_DB_CONTEXT)
+    protected _dbContext: IGlobalDatabaseContext,
+  ) {
+    super();
+  }
+
+  protected async implementation(inputData: GetAuditLogDS): Promise {
+    const { userId, slug, page, limit } = inputData;
+
+    const user = await this._dbContext.userRepository.findOne({
+      where: { id: userId },
+      relations: ['company'],
+    });
+
+    if (!user || !user.company) {
+      throw new NotFoundException(Messages.USER_NOT_FOUND_OR_NOT_IN_COMPANY);
+    }
+
+    const secret = await this._dbContext.userSecretRepository.findSecretBySlugAndCompanyId(slug, user.company.id);
+
+    if (!secret) {
+      throw new NotFoundException(Messages.SECRET_NOT_FOUND);
+    }
+
+    const [logs, total] = await this._dbContext.secretAccessLogRepository.findLogsForSecret(secret.id, {
+      page,
+      limit,
+    });
+
+    return {
+      data: logs.map((log) => buildAuditLogEntryDS(log)),
+      pagination: {
+        total,
+        currentPage: page,
+        perPage: limit,
+        lastPage: Math.ceil(total / limit),
+      },
+    };
+  }
+}
diff --git a/backend/src/entities/user-secret/use-cases/get-secret-by-slug.use.case.ts b/backend/src/entities/user-secret/use-cases/get-secret-by-slug.use.case.ts
new file mode 100644
index 00000000..3e73793d
--- /dev/null
+++ b/backend/src/entities/user-secret/use-cases/get-secret-by-slug.use.case.ts
@@ -0,0 +1,75 @@
+import { ForbiddenException, GoneException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
+import AbstractUseCase from '../../../common/abstract-use.case.js';
+import { BaseType } from '../../../common/data-injection.tokens.js';
+import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
+import { GetSecretDS } from '../application/data-structures/get-secret.ds.js';
+import { FoundSecretDS } from '../application/data-structures/found-secret.ds.js';
+import { IGetSecretBySlug } from './user-secret-use-cases.interface.js';
+import { buildFoundSecretDS } from '../utils/build-found-secret.ds.js';
+import { Encryptor } from '../../../helpers/encryption/encryptor.js';
+import { SecretActionEnum } from '../../secret-access-log/secret-access-log.entity.js';
+import { Messages } from '../../../exceptions/text/messages.js';
+
+@Injectable({ scope: Scope.REQUEST })
+export class GetSecretBySlugUseCase extends AbstractUseCase implements IGetSecretBySlug {
+  constructor(
+    @Inject(BaseType.GLOBAL_DB_CONTEXT)
+    protected _dbContext: IGlobalDatabaseContext,
+  ) {
+    super();
+  }
+
+  protected async implementation(inputData: GetSecretDS): Promise {
+    const { userId, slug, masterPassword } = inputData;
+
+    const user = await this._dbContext.userRepository.findOne({
+      where: { id: userId },
+      relations: ['company'],
+    });
+
+    if (!user || !user.company) {
+      throw new NotFoundException(Messages.USER_NOT_FOUND_OR_NOT_IN_COMPANY);
+    }
+
+    const secret = await this._dbContext.userSecretRepository.findSecretBySlugAndCompanyId(slug, user.company.id);
+
+    if (!secret) {
+      throw new NotFoundException(Messages.SECRET_NOT_FOUND);
+    }
+
+    if (secret.expiresAt && secret.expiresAt < new Date()) {
+      throw new GoneException(Messages.SECRET_EXPIRED);
+    }
+
+    if (secret.masterEncryption && !masterPassword) {
+      throw new ForbiddenException(Messages.SECRET_MASTER_PASSWORD_REQUIRED);
+    }
+
+    if (secret.masterEncryption && masterPassword) {
+      const isValid = await Encryptor.verifyUserPassword(masterPassword, secret.masterHash);
+      if (!isValid) {
+        await this._dbContext.secretAccessLogRepository.createAccessLog(
+          secret.id,
+          userId,
+          SecretActionEnum.VIEW,
+          false,
+          Messages.SECRET_MASTER_PASSWORD_INVALID,
+        );
+        throw new ForbiddenException(Messages.SECRET_MASTER_PASSWORD_INVALID);
+      }
+    }
+
+    secret.lastAccessedAt = new Date();
+    await this._dbContext.userSecretRepository.save(secret);
+
+    await this._dbContext.secretAccessLogRepository.createAccessLog(secret.id, userId, SecretActionEnum.VIEW);
+
+    let decryptedValue = Encryptor.decryptData(secret.encryptedValue);
+
+    if (secret.masterEncryption && masterPassword) {
+      decryptedValue = Encryptor.decryptDataMasterPwd(decryptedValue, masterPassword);
+    }
+
+    return buildFoundSecretDS(secret, decryptedValue);
+  }
+}
diff --git a/backend/src/entities/user-secret/use-cases/get-secrets.use.case.ts b/backend/src/entities/user-secret/use-cases/get-secrets.use.case.ts
new file mode 100644
index 00000000..947aa78f
--- /dev/null
+++ b/backend/src/entities/user-secret/use-cases/get-secrets.use.case.ts
@@ -0,0 +1,47 @@
+import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
+import AbstractUseCase from '../../../common/abstract-use.case.js';
+import { BaseType } from '../../../common/data-injection.tokens.js';
+import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
+import { GetSecretsDS, SecretsListDS } from '../application/data-structures/get-secrets.ds.js';
+import { IGetSecrets } from './user-secret-use-cases.interface.js';
+import { buildSecretListItemDS } from '../utils/build-secret-list-item.ds.js';
+import { Messages } from '../../../exceptions/text/messages.js';
+
+@Injectable({ scope: Scope.REQUEST })
+export class GetSecretsUseCase extends AbstractUseCase implements IGetSecrets {
+  constructor(
+    @Inject(BaseType.GLOBAL_DB_CONTEXT)
+    protected _dbContext: IGlobalDatabaseContext,
+  ) {
+    super();
+  }
+
+  protected async implementation(inputData: GetSecretsDS): Promise {
+    const { userId, page, limit, search } = inputData;
+
+    const user = await this._dbContext.userRepository.findOne({
+      where: { id: userId },
+      relations: ['company'],
+    });
+
+    if (!user || !user.company) {
+      throw new NotFoundException(Messages.USER_NOT_FOUND_OR_NOT_IN_COMPANY);
+    }
+
+    const [secrets, total] = await this._dbContext.userSecretRepository.findSecretsForCompany(user.company.id, {
+      page,
+      limit,
+      search,
+    });
+
+    return {
+      data: secrets.map((secret) => buildSecretListItemDS(secret)),
+      pagination: {
+        total,
+        currentPage: page,
+        perPage: limit,
+        lastPage: Math.ceil(total / limit),
+      },
+    };
+  }
+}
diff --git a/backend/src/entities/user-secret/use-cases/update-secret.use.case.ts b/backend/src/entities/user-secret/use-cases/update-secret.use.case.ts
new file mode 100644
index 00000000..b72ddb63
--- /dev/null
+++ b/backend/src/entities/user-secret/use-cases/update-secret.use.case.ts
@@ -0,0 +1,82 @@
+import { ForbiddenException, GoneException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
+import AbstractUseCase from '../../../common/abstract-use.case.js';
+import { BaseType } from '../../../common/data-injection.tokens.js';
+import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
+import { UpdateSecretDS, UpdatedSecretDS } from '../application/data-structures/update-secret.ds.js';
+import { IUpdateSecret } from './user-secret-use-cases.interface.js';
+import { buildUpdatedSecretDS } from '../utils/build-updated-secret.ds.js';
+import { Encryptor } from '../../../helpers/encryption/encryptor.js';
+import { SecretActionEnum } from '../../secret-access-log/secret-access-log.entity.js';
+import { Messages } from '../../../exceptions/text/messages.js';
+
+@Injectable({ scope: Scope.REQUEST })
+export class UpdateSecretUseCase extends AbstractUseCase implements IUpdateSecret {
+  constructor(
+    @Inject(BaseType.GLOBAL_DB_CONTEXT)
+    protected _dbContext: IGlobalDatabaseContext,
+  ) {
+    super();
+  }
+
+  protected async implementation(inputData: UpdateSecretDS): Promise {
+    const { userId, slug, value, expiresAt, masterPassword } = inputData;
+
+    const user = await this._dbContext.userRepository.findOne({
+      where: { id: userId },
+      relations: ['company'],
+    });
+
+    if (!user || !user.company) {
+      throw new NotFoundException(Messages.USER_NOT_FOUND_OR_NOT_IN_COMPANY);
+    }
+
+    const secret = await this._dbContext.userSecretRepository.findSecretBySlugAndCompanyId(slug, user.company.id);
+
+    if (!secret) {
+      throw new NotFoundException(Messages.SECRET_NOT_FOUND);
+    }
+
+    if (secret.expiresAt && secret.expiresAt < new Date()) {
+      throw new GoneException(Messages.SECRET_EXPIRED);
+    }
+
+    if (secret.masterEncryption && !masterPassword) {
+      throw new ForbiddenException(Messages.SECRET_MASTER_PASSWORD_REQUIRED);
+    }
+
+    if (secret.masterEncryption && masterPassword) {
+      const isValid = await Encryptor.verifyUserPassword(masterPassword, secret.masterHash);
+      if (!isValid) {
+        await this._dbContext.secretAccessLogRepository.createAccessLog(
+          secret.id,
+          userId,
+          SecretActionEnum.UPDATE,
+          false,
+          Messages.SECRET_MASTER_PASSWORD_INVALID,
+        );
+        throw new ForbiddenException(Messages.SECRET_MASTER_PASSWORD_INVALID);
+      }
+    }
+
+    if (value) {
+      let encryptedValue = value;
+
+      if (secret.masterEncryption && masterPassword) {
+        encryptedValue = Encryptor.encryptDataMasterPwd(encryptedValue, masterPassword);
+      }
+
+      encryptedValue = Encryptor.encryptData(encryptedValue);
+      secret.encryptedValue = encryptedValue;
+    }
+
+    if (expiresAt !== undefined) {
+      secret.expiresAt = expiresAt ? new Date(expiresAt) : null;
+    }
+
+    const updated = await this._dbContext.userSecretRepository.save(secret);
+
+    await this._dbContext.secretAccessLogRepository.createAccessLog(secret.id, userId, SecretActionEnum.UPDATE);
+
+    return buildUpdatedSecretDS(updated);
+  }
+}
diff --git a/backend/src/entities/user-secret/use-cases/user-secret-use-cases.interface.ts b/backend/src/entities/user-secret/use-cases/user-secret-use-cases.interface.ts
new file mode 100644
index 00000000..ee1472ae
--- /dev/null
+++ b/backend/src/entities/user-secret/use-cases/user-secret-use-cases.interface.ts
@@ -0,0 +1,33 @@
+import { InTransactionEnum } from '../../../enums/in-transaction.enum.js';
+import { CreateSecretDS } from '../application/data-structures/create-secret.ds.js';
+import { CreatedSecretDS } from '../application/data-structures/created-secret.ds.js';
+import { DeleteSecretDS, DeletedSecretDS } from '../application/data-structures/delete-secret.ds.js';
+import { FoundSecretDS } from '../application/data-structures/found-secret.ds.js';
+import { AuditLogListDS, GetAuditLogDS } from '../application/data-structures/get-audit-log.ds.js';
+import { GetSecretDS } from '../application/data-structures/get-secret.ds.js';
+import { GetSecretsDS, SecretsListDS } from '../application/data-structures/get-secrets.ds.js';
+import { UpdatedSecretDS, UpdateSecretDS } from '../application/data-structures/update-secret.ds.js';
+
+export interface ICreateSecret {
+  execute(inputData: CreateSecretDS, inTransaction: InTransactionEnum): Promise;
+}
+
+export interface IGetSecrets {
+  execute(inputData: GetSecretsDS): Promise;
+}
+
+export interface IGetSecretBySlug {
+  execute(inputData: GetSecretDS): Promise;
+}
+
+export interface IUpdateSecret {
+  execute(inputData: UpdateSecretDS, inTransaction: InTransactionEnum): Promise;
+}
+
+export interface IDeleteSecret {
+  execute(inputData: DeleteSecretDS, inTransaction: InTransactionEnum): Promise;
+}
+
+export interface IGetSecretAuditLog {
+  execute(inputData: GetAuditLogDS): Promise;
+}
diff --git a/backend/src/entities/user-secret/user-secret.controller.ts b/backend/src/entities/user-secret/user-secret.controller.ts
new file mode 100644
index 00000000..abdd2ec7
--- /dev/null
+++ b/backend/src/entities/user-secret/user-secret.controller.ts
@@ -0,0 +1,249 @@
+import {
+  Body,
+  Controller,
+  Delete,
+  Get,
+  HttpCode,
+  HttpStatus,
+  Inject,
+  Injectable,
+  Param,
+  Post,
+  Put,
+  Query,
+  UseGuards,
+  UseInterceptors,
+} from '@nestjs/common';
+import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
+import { UserId } from '../../decorators/user-id.decorator.js';
+import { MasterPassword } from '../../decorators/master-password.decorator.js';
+import { SentryInterceptor } from '../../interceptors/sentry.interceptor.js';
+import { CompanyUserGuard } from '../../guards/company-user.guard.js';
+import { UseCaseType } from '../../common/data-injection.tokens.js';
+import { InTransactionEnum } from '../../enums/in-transaction.enum.js';
+import { CreateSecretDto } from './application/dto/create-secret.dto.js';
+import { UpdateSecretDto } from './application/dto/update-secret.dto.js';
+import { FoundSecretDto } from './application/dto/found-secret.dto.js';
+import { SecretListResponseDto } from './application/dto/secret-list.dto.js';
+import { AuditLogResponseDto } from './application/dto/audit-log.dto.js';
+import { DeleteSecretResponseDto } from './application/dto/delete-secret.dto.js';
+import {
+  ICreateSecret,
+  IDeleteSecret,
+  IGetSecretAuditLog,
+  IGetSecretBySlug,
+  IGetSecrets,
+  IUpdateSecret,
+} from './use-cases/user-secret-use-cases.interface.js';
+import { buildCreatedSecretDto } from './utils/build-created-secret.dto.js';
+import { buildFoundSecretDto } from './utils/build-found-secret.dto.js';
+import { buildUpdatedSecretDto } from './utils/build-updated-secret.dto.js';
+import { buildSecretListResponseDto } from './utils/build-secret-list.dto.js';
+import { buildAuditLogResponseDto } from './utils/build-audit-log.dto.js';
+
+@UseInterceptors(SentryInterceptor)
+@Controller()
+@ApiBearerAuth()
+@ApiTags('Secrets')
+@Injectable()
+export class UserSecretController {
+  constructor(
+    @Inject(UseCaseType.CREATE_SECRET)
+    private readonly createSecretUseCase: ICreateSecret,
+    @Inject(UseCaseType.GET_SECRETS)
+    private readonly getSecretsUseCase: IGetSecrets,
+    @Inject(UseCaseType.GET_SECRET_BY_SLUG)
+    private readonly getSecretBySlugUseCase: IGetSecretBySlug,
+    @Inject(UseCaseType.UPDATE_SECRET)
+    private readonly updateSecretUseCase: IUpdateSecret,
+    @Inject(UseCaseType.DELETE_SECRET)
+    private readonly deleteSecretUseCase: IDeleteSecret,
+    @Inject(UseCaseType.GET_SECRET_AUDIT_LOG)
+    private readonly getSecretAuditLogUseCase: IGetSecretAuditLog,
+  ) {}
+
+  @ApiOperation({ summary: 'Create new secret' })
+  @ApiBody({ type: CreateSecretDto })
+  @ApiResponse({
+    status: 201,
+    description: 'Secret created successfully.',
+    type: FoundSecretDto,
+  })
+  @ApiResponse({
+    status: 404,
+    description: 'User not found or not in a company.',
+  })
+  @ApiResponse({
+    status: 409,
+    description: 'Secret with this slug already exists in your company.',
+  })
+  @UseGuards(CompanyUserGuard)
+  @Post('/secrets')
+  @HttpCode(HttpStatus.CREATED)
+  async createSecret(@UserId() userId: string, @Body() createDto: CreateSecretDto): Promise {
+    const createdSecret = await this.createSecretUseCase.execute(
+      {
+        userId,
+        slug: createDto.slug,
+        value: createDto.value,
+        expiresAt: createDto.expiresAt,
+        masterEncryption: createDto.masterEncryption,
+        masterPassword: createDto.masterPassword,
+      },
+      InTransactionEnum.ON,
+    );
+    return buildCreatedSecretDto(createdSecret);
+  }
+
+  @ApiOperation({ summary: 'Get all company secrets' })
+  @ApiResponse({
+    status: 200,
+    description: 'Returns list of company secrets.',
+    type: SecretListResponseDto,
+  })
+  @ApiResponse({
+    status: 404,
+    description: 'User not found or not in a company.',
+  })
+  @ApiQuery({ name: 'page', required: false, type: Number, description: 'Page number (default: 1)' })
+  @ApiQuery({ name: 'limit', required: false, type: Number, description: 'Items per page (default: 20)' })
+  @ApiQuery({ name: 'search', required: false, type: String, description: 'Search term to filter secrets by slug' })
+  @UseGuards(CompanyUserGuard)
+  @Get('/secrets')
+  async getSecrets(
+    @UserId() userId: string,
+    @Query('page') page?: number,
+    @Query('limit') limit?: number,
+    @Query('search') search?: string,
+  ): Promise {
+    const secretsList = await this.getSecretsUseCase.execute({
+      userId,
+      page: page || 1,
+      limit: limit || 20,
+      search,
+    });
+    return buildSecretListResponseDto(secretsList);
+  }
+
+  @ApiOperation({ summary: 'Get secret by slug' })
+  @ApiResponse({
+    status: 200,
+    description: 'Returns secret details with decrypted value.',
+    type: FoundSecretDto,
+  })
+  @ApiResponse({
+    status: 403,
+    description: 'Master password required or incorrect.',
+  })
+  @ApiResponse({
+    status: 404,
+    description: 'Secret or user not found.',
+  })
+  @ApiResponse({
+    status: 410,
+    description: 'Secret has expired.',
+  })
+  @ApiParam({ name: 'slug', type: String, description: 'Unique secret identifier', example: 'database-password' })
+  @UseGuards(CompanyUserGuard)
+  @Get('/secrets/:slug')
+  async getSecretBySlug(
+    @UserId() userId: string,
+    @Param('slug') slug: string,
+    @MasterPassword() masterPassword?: string,
+  ): Promise {
+    const foundSecret = await this.getSecretBySlugUseCase.execute({
+      userId,
+      slug,
+      masterPassword,
+    });
+    return buildFoundSecretDto(foundSecret);
+  }
+
+  @ApiOperation({ summary: 'Update secret' })
+  @ApiBody({ type: UpdateSecretDto })
+  @ApiResponse({
+    status: 200,
+    description: 'Secret updated successfully.',
+    type: FoundSecretDto,
+  })
+  @ApiResponse({
+    status: 403,
+    description: 'Master password required or incorrect.',
+  })
+  @ApiResponse({
+    status: 404,
+    description: 'Secret or user not found.',
+  })
+  @ApiResponse({
+    status: 410,
+    description: 'Secret has expired.',
+  })
+  @ApiParam({ name: 'slug', type: String, description: 'Unique secret identifier', example: 'database-password' })
+  @UseGuards(CompanyUserGuard)
+  @Put('/secrets/:slug')
+  async updateSecret(
+    @UserId() userId: string,
+    @Param('slug') slug: string,
+    @Body() updateDto: UpdateSecretDto,
+    @MasterPassword() masterPassword?: string,
+  ): Promise {
+    const updatedSecret = await this.updateSecretUseCase.execute(
+      {
+        userId,
+        slug,
+        value: updateDto.value,
+        expiresAt: updateDto.expiresAt,
+        masterPassword,
+      },
+      InTransactionEnum.ON,
+    );
+    return buildUpdatedSecretDto(updatedSecret);
+  }
+
+  @ApiOperation({ summary: 'Delete secret' })
+  @ApiResponse({
+    status: 200,
+    description: 'Secret deleted successfully.',
+    type: DeleteSecretResponseDto,
+  })
+  @ApiResponse({
+    status: 404,
+    description: 'Secret or user not found.',
+  })
+  @ApiParam({ name: 'slug', type: String, description: 'Unique secret identifier', example: 'database-password' })
+  @UseGuards(CompanyUserGuard)
+  @Delete('/secrets/:slug')
+  async deleteSecret(@UserId() userId: string, @Param('slug') slug: string): Promise {
+    return await this.deleteSecretUseCase.execute({ userId, slug }, InTransactionEnum.ON);
+  }
+
+  @ApiOperation({ summary: 'Get secret audit log' })
+  @ApiResponse({
+    status: 200,
+    description: 'Returns audit log for the secret.',
+    type: AuditLogResponseDto,
+  })
+  @ApiResponse({
+    status: 404,
+    description: 'Secret or user not found.',
+  })
+  @ApiParam({ name: 'slug', type: String, description: 'Unique secret identifier', example: 'database-password' })
+  @ApiQuery({ name: 'page', required: false, type: Number, description: 'Page number (default: 1)' })
+  @ApiQuery({ name: 'limit', required: false, type: Number, description: 'Items per page (default: 50)' })
+  @UseGuards(CompanyUserGuard)
+  @Get('/secrets/:slug/audit-log')
+  async getAuditLog(
+    @UserId() userId: string,
+    @Param('slug') slug: string,
+    @Query('page') page?: number,
+    @Query('limit') limit?: number,
+  ): Promise {
+    const auditLog = await this.getSecretAuditLogUseCase.execute({
+      userId,
+      slug,
+      page: page || 1,
+      limit: limit || 50,
+    });
+    return buildAuditLogResponseDto(auditLog);
+  }
+}
diff --git a/backend/src/entities/user-secret/user-secret.entity.ts b/backend/src/entities/user-secret/user-secret.entity.ts
new file mode 100644
index 00000000..1a9faadd
--- /dev/null
+++ b/backend/src/entities/user-secret/user-secret.entity.ts
@@ -0,0 +1,56 @@
+import {
+  Column,
+  CreateDateColumn,
+  Entity,
+  Index,
+  JoinColumn,
+  ManyToOne,
+  OneToMany,
+  PrimaryGeneratedColumn,
+  Relation,
+  UpdateDateColumn,
+} from 'typeorm';
+import { CompanyInfoEntity } from '../company-info/company-info.entity.js';
+import { SecretAccessLogEntity } from '../secret-access-log/secret-access-log.entity.js';
+
+@Entity('user_secrets')
+@Index(['companyId', 'slug'], { unique: true })
+export class UserSecretEntity {
+  @PrimaryGeneratedColumn('uuid')
+  id: string;
+
+  @ManyToOne(() => CompanyInfoEntity, (company) => company.secrets, { onDelete: 'CASCADE' })
+  @JoinColumn({ name: 'companyId' })
+  company: Relation;
+
+  @Column()
+  @Index()
+  companyId: string;
+
+  @Column({ type: 'varchar', length: 255 })
+  slug: string;
+
+  @Column({ type: 'text' })
+  encryptedValue: string;
+
+  @CreateDateColumn()
+  createdAt: Date;
+
+  @UpdateDateColumn()
+  updatedAt: Date;
+
+  @Column({ type: 'timestamp', nullable: true })
+  lastAccessedAt: Date;
+
+  @Column({ type: 'timestamp', nullable: true })
+  expiresAt: Date;
+
+  @Column({ default: false })
+  masterEncryption: boolean;
+
+  @Column({ type: 'varchar', length: 4096, nullable: true })
+  masterHash: string;
+
+  @OneToMany(() => SecretAccessLogEntity, (log) => log.secret)
+  accessLogs: Relation[];
+}
diff --git a/backend/src/entities/user-secret/user-secret.module.ts b/backend/src/entities/user-secret/user-secret.module.ts
new file mode 100644
index 00000000..aa5f1bb8
--- /dev/null
+++ b/backend/src/entities/user-secret/user-secret.module.ts
@@ -0,0 +1,81 @@
+import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { UserSecretEntity } from './user-secret.entity.js';
+import { SecretAccessLogEntity } from '../secret-access-log/secret-access-log.entity.js';
+import { UserEntity } from '../user/user.entity.js';
+import { UserSecretController } from './user-secret.controller.js';
+import { AuthMiddleware } from '../../authorization/auth.middleware.js';
+import { LogOutEntity } from '../log-out/log-out.entity.js';
+import { GlobalDatabaseContext } from '../../common/application/global-database-context.js';
+import { BaseType, UseCaseType } from '../../common/data-injection.tokens.js';
+import { CreateSecretUseCase } from './use-cases/create-secret.use.case.js';
+import { GetSecretsUseCase } from './use-cases/get-secrets.use.case.js';
+import { GetSecretBySlugUseCase } from './use-cases/get-secret-by-slug.use.case.js';
+import { UpdateSecretUseCase } from './use-cases/update-secret.use.case.js';
+import { DeleteSecretUseCase } from './use-cases/delete-secret.use.case.js';
+import { GetSecretAuditLogUseCase } from './use-cases/get-secret-audit-log.use.case.js';
+
+@Module({
+  imports: [TypeOrmModule.forFeature([UserSecretEntity, SecretAccessLogEntity, UserEntity, LogOutEntity])],
+  providers: [
+    {
+      provide: BaseType.GLOBAL_DB_CONTEXT,
+      useClass: GlobalDatabaseContext,
+    },
+    {
+      provide: UseCaseType.CREATE_SECRET,
+      useClass: CreateSecretUseCase,
+    },
+    {
+      provide: UseCaseType.GET_SECRETS,
+      useClass: GetSecretsUseCase,
+    },
+    {
+      provide: UseCaseType.GET_SECRET_BY_SLUG,
+      useClass: GetSecretBySlugUseCase,
+    },
+    {
+      provide: UseCaseType.UPDATE_SECRET,
+      useClass: UpdateSecretUseCase,
+    },
+    {
+      provide: UseCaseType.DELETE_SECRET,
+      useClass: DeleteSecretUseCase,
+    },
+    {
+      provide: UseCaseType.GET_SECRET_AUDIT_LOG,
+      useClass: GetSecretAuditLogUseCase,
+    },
+  ],
+  controllers: [UserSecretController],
+})
+export class UserSecretModule implements NestModule {
+  public configure(consumer: MiddlewareConsumer): void {
+    consumer.apply(AuthMiddleware).forRoutes(
+      {
+        path: '/secrets',
+        method: RequestMethod.POST,
+      },
+      {
+        path: '/secrets',
+        method: RequestMethod.GET,
+      },
+      {
+        path: '/secrets/:slug',
+        method: RequestMethod.GET,
+      },
+      {
+        path: '/secrets/:slug',
+        method: RequestMethod.PUT,
+      },
+      {
+        path: '/secrets/:slug',
+        method: RequestMethod.DELETE,
+      },
+      {
+        path: '/secrets/:slug/audit-log',
+        method: RequestMethod.GET,
+      },
+    );
+  }
+}
diff --git a/backend/src/entities/user-secret/utils/build-audit-log-entry.ds.ts b/backend/src/entities/user-secret/utils/build-audit-log-entry.ds.ts
new file mode 100644
index 00000000..8112f8d2
--- /dev/null
+++ b/backend/src/entities/user-secret/utils/build-audit-log-entry.ds.ts
@@ -0,0 +1,18 @@
+import { SecretAccessLogEntity } from '../../secret-access-log/secret-access-log.entity.js';
+import { AuditLogEntryDS } from '../application/data-structures/get-audit-log.ds.js';
+
+export function buildAuditLogEntryDS(log: SecretAccessLogEntity): AuditLogEntryDS {
+  return {
+    id: log.id,
+    action: log.action,
+    user: {
+      id: log.user.id,
+      email: log.user.email,
+    },
+    accessedAt: log.accessedAt,
+    ipAddress: log.ipAddress,
+    userAgent: log.userAgent,
+    success: log.success,
+    errorMessage: log.errorMessage,
+  };
+}
diff --git a/backend/src/entities/user-secret/utils/build-audit-log.dto.ts b/backend/src/entities/user-secret/utils/build-audit-log.dto.ts
new file mode 100644
index 00000000..67a31561
--- /dev/null
+++ b/backend/src/entities/user-secret/utils/build-audit-log.dto.ts
@@ -0,0 +1,22 @@
+import { AuditLogResponseDto, AuditLogEntryDto } from '../application/dto/audit-log.dto.js';
+import { AuditLogListDS, AuditLogEntryDS } from '../application/data-structures/get-audit-log.ds.js';
+
+export function buildAuditLogEntryDto(ds: AuditLogEntryDS): AuditLogEntryDto {
+  return {
+    id: ds.id,
+    action: ds.action,
+    user: ds.user,
+    accessedAt: ds.accessedAt,
+    ipAddress: ds.ipAddress,
+    userAgent: ds.userAgent,
+    success: ds.success,
+    errorMessage: ds.errorMessage,
+  };
+}
+
+export function buildAuditLogResponseDto(ds: AuditLogListDS): AuditLogResponseDto {
+  return {
+    data: ds.data.map(buildAuditLogEntryDto),
+    pagination: ds.pagination,
+  };
+}
diff --git a/backend/src/entities/user-secret/utils/build-created-secret.ds.ts b/backend/src/entities/user-secret/utils/build-created-secret.ds.ts
new file mode 100644
index 00000000..9a55c4d4
--- /dev/null
+++ b/backend/src/entities/user-secret/utils/build-created-secret.ds.ts
@@ -0,0 +1,15 @@
+import { CreatedSecretDS } from '../application/data-structures/created-secret.ds.js';
+import { UserSecretEntity } from '../user-secret.entity.js';
+
+export function buildCreatedSecretDS(secret: UserSecretEntity): CreatedSecretDS {
+  return {
+    id: secret.id,
+    slug: secret.slug,
+    companyId: secret.companyId,
+    createdAt: secret.createdAt,
+    updatedAt: secret.updatedAt,
+    lastAccessedAt: secret.lastAccessedAt,
+    expiresAt: secret.expiresAt,
+    masterEncryption: secret.masterEncryption,
+  };
+}
diff --git a/backend/src/entities/user-secret/utils/build-created-secret.dto.ts b/backend/src/entities/user-secret/utils/build-created-secret.dto.ts
new file mode 100644
index 00000000..fdfbdc9d
--- /dev/null
+++ b/backend/src/entities/user-secret/utils/build-created-secret.dto.ts
@@ -0,0 +1,16 @@
+import { FoundSecretDto } from '../application/dto/found-secret.dto.js';
+import { CreatedSecretDS } from '../application/data-structures/created-secret.ds.js';
+
+export function buildCreatedSecretDto(ds: CreatedSecretDS): FoundSecretDto {
+  return {
+    id: ds.id,
+    slug: ds.slug,
+    value: undefined,
+    companyId: ds.companyId,
+    createdAt: ds.createdAt,
+    updatedAt: ds.updatedAt,
+    lastAccessedAt: ds.lastAccessedAt,
+    expiresAt: ds.expiresAt,
+    masterEncryption: ds.masterEncryption,
+  };
+}
diff --git a/backend/src/entities/user-secret/utils/build-found-secret.ds.ts b/backend/src/entities/user-secret/utils/build-found-secret.ds.ts
new file mode 100644
index 00000000..898800d6
--- /dev/null
+++ b/backend/src/entities/user-secret/utils/build-found-secret.ds.ts
@@ -0,0 +1,16 @@
+import { FoundSecretDS } from '../application/data-structures/found-secret.ds.js';
+import { UserSecretEntity } from '../user-secret.entity.js';
+
+export function buildFoundSecretDS(secret: UserSecretEntity, decryptedValue?: string): FoundSecretDS {
+  return {
+    id: secret.id,
+    slug: secret.slug,
+    value: decryptedValue,
+    companyId: secret.companyId,
+    createdAt: secret.createdAt,
+    updatedAt: secret.updatedAt,
+    lastAccessedAt: secret.lastAccessedAt,
+    expiresAt: secret.expiresAt,
+    masterEncryption: secret.masterEncryption,
+  };
+}
diff --git a/backend/src/entities/user-secret/utils/build-found-secret.dto.ts b/backend/src/entities/user-secret/utils/build-found-secret.dto.ts
new file mode 100644
index 00000000..81504154
--- /dev/null
+++ b/backend/src/entities/user-secret/utils/build-found-secret.dto.ts
@@ -0,0 +1,16 @@
+import { FoundSecretDto } from '../application/dto/found-secret.dto.js';
+import { FoundSecretDS } from '../application/data-structures/found-secret.ds.js';
+
+export function buildFoundSecretDto(ds: FoundSecretDS): FoundSecretDto {
+  return {
+    id: ds.id,
+    slug: ds.slug,
+    value: ds.value,
+    companyId: ds.companyId,
+    createdAt: ds.createdAt,
+    updatedAt: ds.updatedAt,
+    lastAccessedAt: ds.lastAccessedAt,
+    expiresAt: ds.expiresAt,
+    masterEncryption: ds.masterEncryption,
+  };
+}
diff --git a/backend/src/entities/user-secret/utils/build-secret-list-item.ds.ts b/backend/src/entities/user-secret/utils/build-secret-list-item.ds.ts
new file mode 100644
index 00000000..37b63eb9
--- /dev/null
+++ b/backend/src/entities/user-secret/utils/build-secret-list-item.ds.ts
@@ -0,0 +1,15 @@
+import { SecretListItemDS } from '../application/data-structures/get-secrets.ds.js';
+import { UserSecretEntity } from '../user-secret.entity.js';
+
+export function buildSecretListItemDS(secret: UserSecretEntity): SecretListItemDS {
+  return {
+    id: secret.id,
+    slug: secret.slug,
+    companyId: secret.companyId,
+    createdAt: secret.createdAt,
+    updatedAt: secret.updatedAt,
+    lastAccessedAt: secret.lastAccessedAt,
+    expiresAt: secret.expiresAt,
+    masterEncryption: secret.masterEncryption,
+  };
+}
diff --git a/backend/src/entities/user-secret/utils/build-secret-list.dto.ts b/backend/src/entities/user-secret/utils/build-secret-list.dto.ts
new file mode 100644
index 00000000..add45f48
--- /dev/null
+++ b/backend/src/entities/user-secret/utils/build-secret-list.dto.ts
@@ -0,0 +1,22 @@
+import { SecretListResponseDto, SecretListItemDto } from '../application/dto/secret-list.dto.js';
+import { SecretsListDS, SecretListItemDS } from '../application/data-structures/get-secrets.ds.js';
+
+export function buildSecretListItemDto(ds: SecretListItemDS): SecretListItemDto {
+  return {
+    id: ds.id,
+    slug: ds.slug,
+    companyId: ds.companyId,
+    createdAt: ds.createdAt,
+    updatedAt: ds.updatedAt,
+    lastAccessedAt: ds.lastAccessedAt,
+    expiresAt: ds.expiresAt,
+    masterEncryption: ds.masterEncryption,
+  };
+}
+
+export function buildSecretListResponseDto(ds: SecretsListDS): SecretListResponseDto {
+  return {
+    data: ds.data.map(buildSecretListItemDto),
+    pagination: ds.pagination,
+  };
+}
diff --git a/backend/src/entities/user-secret/utils/build-updated-secret.ds.ts b/backend/src/entities/user-secret/utils/build-updated-secret.ds.ts
new file mode 100644
index 00000000..d42b1932
--- /dev/null
+++ b/backend/src/entities/user-secret/utils/build-updated-secret.ds.ts
@@ -0,0 +1,15 @@
+import { UpdatedSecretDS } from '../application/data-structures/update-secret.ds.js';
+import { UserSecretEntity } from '../user-secret.entity.js';
+
+export function buildUpdatedSecretDS(secret: UserSecretEntity): UpdatedSecretDS {
+  return {
+    id: secret.id,
+    slug: secret.slug,
+    companyId: secret.companyId,
+    createdAt: secret.createdAt,
+    updatedAt: secret.updatedAt,
+    lastAccessedAt: secret.lastAccessedAt,
+    expiresAt: secret.expiresAt,
+    masterEncryption: secret.masterEncryption,
+  };
+}
diff --git a/backend/src/entities/user-secret/utils/build-updated-secret.dto.ts b/backend/src/entities/user-secret/utils/build-updated-secret.dto.ts
new file mode 100644
index 00000000..3fd9dce0
--- /dev/null
+++ b/backend/src/entities/user-secret/utils/build-updated-secret.dto.ts
@@ -0,0 +1,16 @@
+import { FoundSecretDto } from '../application/dto/found-secret.dto.js';
+import { UpdatedSecretDS } from '../application/data-structures/update-secret.ds.js';
+
+export function buildUpdatedSecretDto(ds: UpdatedSecretDS): FoundSecretDto {
+  return {
+    id: ds.id,
+    slug: ds.slug,
+    value: undefined,
+    companyId: ds.companyId,
+    createdAt: ds.createdAt,
+    updatedAt: ds.updatedAt,
+    lastAccessedAt: ds.lastAccessedAt,
+    expiresAt: ds.expiresAt,
+    masterEncryption: ds.masterEncryption,
+  };
+}
diff --git a/backend/src/entities/user/user.entity.ts b/backend/src/entities/user/user.entity.ts
index 91e5d290..f73a96ed 100644
--- a/backend/src/entities/user/user.entity.ts
+++ b/backend/src/entities/user/user.entity.ts
@@ -27,6 +27,7 @@ import { ExternalRegistrationProviderEnum } from './enums/external-registration-
 import { UserApiKeyEntity } from '../api-key/api-key.entity.js';
 import { AiResponsesToUserEntity } from '../ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user.entity.js';
 import { SignInAuditEntity } from '../user-sign-in-audit/sign-in-audit.entity.js';
+import { SecretAccessLogEntity } from '../secret-access-log/secret-access-log.entity.js';
 
 @Entity('user')
 export class UserEntity {
@@ -123,6 +124,9 @@ export class UserEntity {
   @OneToMany((_) => SignInAuditEntity, (signInAudit) => signInAudit.user)
   signInAudits: Relation[];
 
+  @OneToMany((_) => SecretAccessLogEntity, (secretAccessLog) => secretAccessLog.user)
+  secretAccessLogs: Relation[];
+
   @Column({ default: false, type: 'boolean' })
   isActive: boolean;
 
diff --git a/backend/src/exceptions/text/messages.ts b/backend/src/exceptions/text/messages.ts
index 6dcab920..64971af8 100644
--- a/backend/src/exceptions/text/messages.ts
+++ b/backend/src/exceptions/text/messages.ts
@@ -370,4 +370,11 @@ export const Messages = {
   INVALID_REQUEST_DOMAIN: `Invalid request domain`,
   INVALID_REQUEST_DOMAIN_FORMAT: `Invalid request domain format`,
   FEATURE_NON_AVAILABLE_IN_FREE_PLAN: `This feature is not available in free plan.`,
+  SECRET_NOT_FOUND: 'Secret not found',
+  SECRET_ALREADY_EXISTS: 'Secret with this slug already exists in your company',
+  SECRET_EXPIRED: 'Secret has expired',
+  SECRET_MASTER_PASSWORD_REQUIRED: 'Master password required',
+  SECRET_MASTER_PASSWORD_INVALID: 'Invalid master password',
+  SECRET_DELETED_SUCCESSFULLY: 'Secret deleted successfully',
+  USER_NOT_FOUND_OR_NOT_IN_COMPANY: 'User not found or not associated with a company',
 };
diff --git a/backend/src/migrations/1763724061000-CreateUserSecretEntity.ts b/backend/src/migrations/1763724061000-CreateUserSecretEntity.ts
new file mode 100644
index 00000000..805608c8
--- /dev/null
+++ b/backend/src/migrations/1763724061000-CreateUserSecretEntity.ts
@@ -0,0 +1,38 @@
+import {MigrationInterface, QueryRunner} from "typeorm";
+
+export class CreateUserSecretEntity1763724061000 implements MigrationInterface {
+    name = 'CreateUserSecretEntity1763724061000'
+
+    public async up(queryRunner: QueryRunner): Promise {
+        await queryRunner.query(`CREATE TABLE "user_secrets" (
+            "id" uuid NOT NULL DEFAULT uuid_generate_v4(),
+            "companyId" uuid NOT NULL,
+            "slug" character varying(255) NOT NULL,
+            "encryptedValue" text NOT NULL,
+            "createdAt" TIMESTAMP NOT NULL DEFAULT now(),
+            "updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
+            "lastAccessedAt" TIMESTAMP,
+            "expiresAt" TIMESTAMP,
+            "masterEncryption" boolean NOT NULL DEFAULT false,
+            "masterHash" character varying(4096),
+            CONSTRAINT "PK_user_secrets" PRIMARY KEY ("id")
+        )`);
+
+        await queryRunner.query(`CREATE INDEX "IDX_user_secrets_companyId" ON "user_secrets" ("companyId")`);
+        await queryRunner.query(`CREATE INDEX "IDX_user_secrets_createdAt" ON "user_secrets" ("createdAt")`);
+        await queryRunner.query(`CREATE INDEX "IDX_user_secrets_expiresAt" ON "user_secrets" ("expiresAt")`);
+        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_user_secrets_company_slug" ON "user_secrets" ("companyId", "slug")`);
+
+        await queryRunner.query(`ALTER TABLE "user_secrets" ADD CONSTRAINT "FK_user_secrets_companyId" FOREIGN KEY ("companyId") REFERENCES "company_info"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+    }
+
+    public async down(queryRunner: QueryRunner): Promise {
+        await queryRunner.query(`ALTER TABLE "user_secrets" DROP CONSTRAINT "FK_user_secrets_companyId"`);
+        await queryRunner.query(`DROP INDEX "IDX_user_secrets_company_slug"`);
+        await queryRunner.query(`DROP INDEX "IDX_user_secrets_expiresAt"`);
+        await queryRunner.query(`DROP INDEX "IDX_user_secrets_createdAt"`);
+        await queryRunner.query(`DROP INDEX "IDX_user_secrets_companyId"`);
+        await queryRunner.query(`DROP TABLE "user_secrets"`);
+    }
+
+}
diff --git a/backend/src/migrations/1763724062000-CreateSecretAccessLogEntity.ts b/backend/src/migrations/1763724062000-CreateSecretAccessLogEntity.ts
new file mode 100644
index 00000000..48fb6c39
--- /dev/null
+++ b/backend/src/migrations/1763724062000-CreateSecretAccessLogEntity.ts
@@ -0,0 +1,40 @@
+import {MigrationInterface, QueryRunner} from "typeorm";
+
+export class CreateSecretAccessLogEntity1763724062000 implements MigrationInterface {
+    name = 'CreateSecretAccessLogEntity1763724062000'
+
+    public async up(queryRunner: QueryRunner): Promise {
+        await queryRunner.query(`CREATE TYPE "secret_access_logs_action_enum" AS ENUM('create', 'view', 'copy', 'update', 'delete')`);
+
+        await queryRunner.query(`CREATE TABLE "secret_access_logs" (
+            "id" uuid NOT NULL DEFAULT uuid_generate_v4(),
+            "secretId" uuid NOT NULL,
+            "userId" uuid NOT NULL,
+            "action" "secret_access_logs_action_enum" NOT NULL,
+            "accessedAt" TIMESTAMP NOT NULL DEFAULT now(),
+            "ipAddress" character varying(45),
+            "userAgent" text,
+            "success" boolean NOT NULL DEFAULT true,
+            "errorMessage" text,
+            CONSTRAINT "PK_secret_access_logs" PRIMARY KEY ("id")
+        )`);
+
+        await queryRunner.query(`CREATE INDEX "IDX_secret_access_logs_secretId" ON "secret_access_logs" ("secretId")`);
+        await queryRunner.query(`CREATE INDEX "IDX_secret_access_logs_userId" ON "secret_access_logs" ("userId")`);
+        await queryRunner.query(`CREATE INDEX "IDX_secret_access_logs_accessedAt" ON "secret_access_logs" ("accessedAt")`);
+
+        await queryRunner.query(`ALTER TABLE "secret_access_logs" ADD CONSTRAINT "FK_secret_access_logs_secretId" FOREIGN KEY ("secretId") REFERENCES "user_secrets"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE "secret_access_logs" ADD CONSTRAINT "FK_secret_access_logs_userId" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+    }
+
+    public async down(queryRunner: QueryRunner): Promise {
+        await queryRunner.query(`ALTER TABLE "secret_access_logs" DROP CONSTRAINT "FK_secret_access_logs_userId"`);
+        await queryRunner.query(`ALTER TABLE "secret_access_logs" DROP CONSTRAINT "FK_secret_access_logs_secretId"`);
+        await queryRunner.query(`DROP INDEX "IDX_secret_access_logs_accessedAt"`);
+        await queryRunner.query(`DROP INDEX "IDX_secret_access_logs_userId"`);
+        await queryRunner.query(`DROP INDEX "IDX_secret_access_logs_secretId"`);
+        await queryRunner.query(`DROP TABLE "secret_access_logs"`);
+        await queryRunner.query(`DROP TYPE "secret_access_logs_action_enum"`);
+    }
+
+}
diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-secrets-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-secrets-e2e.test.ts
new file mode 100644
index 00000000..29d2f6b6
--- /dev/null
+++ b/backend/test/ava-tests/non-saas-tests/non-saas-secrets-e2e.test.ts
@@ -0,0 +1,655 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import { INestApplication, ValidationPipe } from '@nestjs/common';
+import { Test } from '@nestjs/testing';
+import test from 'ava';
+import cookieParser from 'cookie-parser';
+import request from 'supertest';
+import { ApplicationModule } from '../../../src/app.module.js';
+import { AllExceptionsFilter } from '../../../src/exceptions/all-exceptions.filter.js';
+import { Cacher } from '../../../src/helpers/cache/cacher.js';
+import { DatabaseModule } from '../../../src/shared/database/database.module.js';
+import { DatabaseService } from '../../../src/shared/database/database.service.js';
+import { MockFactory } from '../../mock.factory.js';
+import { getTestData } from '../../utils/get-test-data.js';
+import { registerUserAndReturnUserInfo } from '../../utils/register-user-and-return-user-info.js';
+import { TestUtils } from '../../utils/test.utils.js';
+import { setSaasEnvVariable } from '../../utils/set-saas-env-variable.js';
+import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js';
+import { ValidationError } from 'class-validator';
+import { WinstonLogger } from '../../../src/entities/logging/winston-logger.js';
+
+const mockFactory = new MockFactory();
+let app: INestApplication;
+let testUtils: TestUtils;
+let currentTest: string;
+
+test.before(async () => {
+  setSaasEnvVariable();
+  const moduleFixture = await Test.createTestingModule({
+    imports: [ApplicationModule, DatabaseModule],
+    providers: [DatabaseService, TestUtils],
+  }).compile();
+  app = moduleFixture.createNestApplication();
+  testUtils = moduleFixture.get(TestUtils);
+
+  app.use(cookieParser());
+  app.useGlobalFilters(new AllExceptionsFilter(app.get(WinstonLogger)));
+  app.useGlobalPipes(
+    new ValidationPipe({
+      exceptionFactory(validationErrors: ValidationError[] = []) {
+        return new ValidationException(validationErrors);
+      },
+    }),
+  );
+  await app.init();
+  app.getHttpServer().listen(0);
+});
+
+test.after(async () => {
+  try {
+    await Cacher.clearAllCache();
+    await app.close();
+  } catch (e) {
+    console.error('After tests error ' + e);
+  }
+});
+
+currentTest = 'POST /secrets';
+test.serial(`${currentTest} - should create a new secret`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const createDto = {
+    slug: 'test-api-key',
+    value: 'sk-test-1234567890',
+    masterEncryption: false,
+  };
+
+  const response = await request(app.getHttpServer())
+    .post('/secrets')
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json')
+    .send(createDto);
+
+  t.is(response.status, 201, response.text);
+  const responseBody = JSON.parse(response.text);
+  t.is(responseBody.slug, 'test-api-key');
+  t.is(responseBody.masterEncryption, false);
+  t.truthy(responseBody.id);
+  t.truthy(responseBody.createdAt);
+  t.falsy(responseBody.value);
+});
+
+test.serial(`${currentTest} - should return 409 for duplicate slug`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const createDto = {
+    slug: 'test-api-key',
+    value: 'sk-another-key',
+  };
+
+  const response = await request(app.getHttpServer())
+    .post('/secrets')
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json')
+    .send(createDto);
+
+  t.is(response.status, 409, response.text);
+  const responseBody = JSON.parse(response.text);
+  t.truthy(responseBody.message);
+});
+
+test.serial(`${currentTest} - should validate slug format`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const createDto = {
+    slug: 'invalid slug with spaces!',
+    value: 'sk-test-key',
+  };
+
+  const response = await request(app.getHttpServer())
+    .post('/secrets')
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json')
+    .send(createDto);
+
+  t.is(response.status, 400, response.text);
+  const responseBody = JSON.parse(response.text);
+  t.truthy(responseBody.message);
+});
+
+currentTest = 'GET /secrets';
+test.serial(`${currentTest} - should return list of company secrets`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const response = await request(app.getHttpServer())
+    .get('/secrets')
+    .set('Cookie', token)
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  t.is(response.status, 200, response.text);
+  const responseBody = JSON.parse(response.text);
+  t.truthy(responseBody.data);
+  t.true(Array.isArray(responseBody.data));
+  t.truthy(responseBody.pagination);
+  t.is(responseBody.pagination.currentPage, 1);
+  t.true(responseBody.data.length > 0);
+
+  const firstSecret = responseBody.data[0];
+  t.falsy(firstSecret.value);
+  t.truthy(firstSecret.slug);
+});
+
+test.serial(`${currentTest}?search=test - should filter secrets by slug`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const response = await request(app.getHttpServer())
+    .get('/secrets?search=test')
+    .set('Cookie', token)
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  t.is(response.status, 200, response.text);
+  const responseBody = JSON.parse(response.text);
+  t.truthy(responseBody.data);
+  t.true(responseBody.data.every((s: any) => s.slug.includes('test')));
+});
+
+currentTest = 'GET /secrets/:slug';
+test.serial(`${currentTest} - should return secret with value`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const response = await request(app.getHttpServer())
+    .get('/secrets/test-api-key')
+    .set('Cookie', token)
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  t.is(response.status, 200, response.text);
+  const responseBody = JSON.parse(response.text);
+  t.is(responseBody.slug, 'test-api-key');
+  t.truthy(responseBody.value);
+  t.truthy(responseBody.lastAccessedAt);
+});
+
+test.serial(`${currentTest} - should return 404 for non-existent secret`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const response = await request(app.getHttpServer())
+    .get('/secrets/non-existent-secret')
+    .set('Cookie', token)
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  t.is(response.status, 404, response.text);
+});
+
+currentTest = 'POST /secrets';
+test.serial(`${currentTest} - should create secret with master password`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const createDto = {
+    slug: 'protected-secret',
+    value: 'sensitive-data',
+    masterEncryption: true,
+    masterPassword: 'MasterPass123!',
+  };
+
+  const response = await request(app.getHttpServer())
+    .post('/secrets')
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json')
+    .send(createDto);
+
+  t.is(response.status, 201, response.text);
+  const responseBody = JSON.parse(response.text);
+  t.is(responseBody.slug, 'protected-secret');
+  t.is(responseBody.masterEncryption, true);
+});
+
+currentTest = 'GET /secrets/:slug';
+test.serial(`${currentTest} - should require master password for protected secret`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const response = await request(app.getHttpServer())
+    .get('/secrets/protected-secret')
+    .set('Cookie', token)
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  t.is(response.status, 403, response.text);
+});
+
+test.serial(`${currentTest} - should return protected secret with correct master password`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const response = await request(app.getHttpServer())
+    .get('/secrets/protected-secret')
+    .set('Cookie', token)
+    .set('masterpwd', 'MasterPass123!')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  t.is(response.status, 200, response.text);
+  const responseBody = JSON.parse(response.text);
+  t.is(responseBody.slug, 'protected-secret');
+  t.truthy(responseBody.value);
+});
+
+currentTest = 'PUT /secrets/:slug';
+test.serial(`${currentTest} - should update secret value`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const updateDto = {
+    value: 'updated-secret-value',
+  };
+
+  const response = await request(app.getHttpServer())
+    .put('/secrets/test-api-key')
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json')
+    .send(updateDto);
+
+  t.is(response.status, 200, response.text);
+  const responseBody = JSON.parse(response.text);
+  t.is(responseBody.slug, 'test-api-key');
+  t.truthy(responseBody.updatedAt);
+});
+
+test.serial(`${currentTest} - should update expiration date`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const futureDate = new Date();
+  futureDate.setFullYear(futureDate.getFullYear() + 1);
+
+  const updateDto = {
+    value: 'updated-secret-value',
+    expiresAt: futureDate.toISOString(),
+  };
+
+  const response = await request(app.getHttpServer())
+    .put('/secrets/test-api-key')
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json')
+    .send(updateDto);
+
+  t.is(response.status, 200, response.text);
+  const responseBody = JSON.parse(response.text);
+  t.truthy(responseBody.expiresAt);
+});
+
+test.serial(`${currentTest} - should return 404 for non-existent secret`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const updateDto = {
+    value: 'new-value',
+  };
+
+  const response = await request(app.getHttpServer())
+    .put('/secrets/non-existent')
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json')
+    .send(updateDto);
+
+  t.is(response.status, 404, response.text);
+});
+
+currentTest = 'GET /secrets/:slug/audit-log';
+test.serial(`${currentTest} - should return audit log entries`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const response = await request(app.getHttpServer())
+    .get('/secrets/test-api-key/audit-log')
+    .set('Cookie', token)
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  t.is(response.status, 200, response.text);
+  const responseBody = JSON.parse(response.text);
+  t.truthy(responseBody.data);
+  t.true(Array.isArray(responseBody.data));
+  t.truthy(responseBody.pagination);
+
+  t.true(responseBody.data.length >= 3);
+
+  const logEntry = responseBody.data[0];
+  t.truthy(logEntry.id);
+  t.truthy(logEntry.action);
+  t.truthy(logEntry.user);
+  t.truthy(logEntry.accessedAt);
+  t.is(logEntry.success, true);
+});
+
+test.serial(`${currentTest}?page=1&limit=2 - should paginate audit log`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const response = await request(app.getHttpServer())
+    .get('/secrets/test-api-key/audit-log?page=1&limit=2')
+    .set('Cookie', token)
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  t.is(response.status, 200, response.text);
+  const responseBody = JSON.parse(response.text);
+  t.is(responseBody.pagination.perPage, '2');
+  t.true(responseBody.data.length <= 2);
+});
+
+currentTest = 'POST /secrets';
+test.serial(`${currentTest} - should create expired secret`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const pastDate = new Date();
+  pastDate.setFullYear(pastDate.getFullYear() - 1);
+
+  const createDto = {
+    slug: 'expired-secret',
+    value: 'expired-value',
+    expiresAt: pastDate.toISOString(),
+  };
+
+  const response = await request(app.getHttpServer())
+    .post('/secrets')
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json')
+    .send(createDto);
+
+  t.is(response.status, 201, response.text);
+});
+
+currentTest = 'GET /secrets/:slug';
+test.serial(`${currentTest} - should return 410 for expired secret`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const response = await request(app.getHttpServer())
+    .get('/secrets/expired-secret')
+    .set('Cookie', token)
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  t.is(response.status, 410, response.text);
+});
+
+currentTest = 'DELETE /secrets/:slug';
+test.serial(`${currentTest} - should delete secret`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const response = await request(app.getHttpServer())
+    .delete('/secrets/test-api-key')
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  t.is(response.status, 200, response.text);
+  const responseBody = JSON.parse(response.text);
+  t.is(responseBody.message, 'Secret deleted successfully');
+  t.truthy(responseBody.deletedAt);
+});
+
+currentTest = 'GET /secrets/:slug';
+test.serial(`${currentTest} - should return 404 after deletion`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const response = await request(app.getHttpServer())
+    .get('/secrets/test-api-key')
+    .set('Cookie', token)
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  t.is(response.status, 404, response.text);
+});
+
+currentTest = 'DELETE /secrets/:slug';
+test.serial(`${currentTest} - should return 404 for non-existent secret`, async (t) => {
+  const { token } = await registerUserAndReturnUserInfo(app);
+  const newConnection = getTestData(mockFactory).newEncryptedConnection;
+  const createdConnection = await request(app.getHttpServer())
+    .post('/connection')
+    .send(newConnection)
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  const connectionId = JSON.parse(createdConnection.text).id;
+
+  const response = await request(app.getHttpServer())
+    .delete('/secrets/non-existent')
+    .set('Cookie', token)
+    .set('masterpwd', 'ahalaimahalai')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  t.is(response.status, 404, response.text);
+});
+
+currentTest = 'GET /secrets';
+test.serial(`${currentTest} - unauthorized without token`, async (t) => {
+  const response = await request(app.getHttpServer())
+    .get('/secrets')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json');
+
+  t.is(response.status, 401, response.text);
+});
+
+currentTest = 'POST /secrets';
+test.serial(`${currentTest} - unauthorized without token`, async (t) => {
+  const createDto = {
+    slug: 'test-secret',
+    value: 'value',
+  };
+
+  const response = await request(app.getHttpServer())
+    .post('/secrets')
+    .set('Content-Type', 'application/json')
+    .set('Accept', 'application/json')
+    .send(createDto);
+
+  t.is(response.status, 401, response.text);
+});
diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-cassandra.e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-cassandra.e2e.test.ts
index 5ac8c76e..0f6626db 100644
--- a/backend/test/ava-tests/non-saas-tests/non-saas-table-cassandra.e2e.test.ts
+++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-cassandra.e2e.test.ts
@@ -27,6 +27,7 @@ import { registerUserAndReturnUserInfo } from '../../utils/register-user-and-ret
 import { TestUtils } from '../../utils/test.utils.js';
 import { setSaasEnvVariable } from '../../utils/set-saas-env-variable.js';
 import { WinstonLogger } from '../../../src/entities/logging/winston-logger.js';
+import os from "node:os";
 
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = path.dirname(__filename);
@@ -3461,12 +3462,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) =
   }
   t.is(getTableCsvResponse.status, 201);
   const fileName = `${testTableName}.csv`;
-  const downloadedFilePatch = join(__dirname, 'response-files', fileName);
+  const downloadedFilePatch = join(os.tmpdir(), fileName);
 
-  const dir = join(__dirname, 'response-files');
-  if (!fs.existsSync(dir)) {
-    fs.mkdirSync(dir);
-  }
   // eslint-disable-next-line security/detect-non-literal-fs-filename
   fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body);
 
@@ -3584,12 +3581,9 @@ with search and pagination: page=1, perPage=2 and DESC sorting`,
     }
     t.is(getTableCsvResponse.status, 201);
     const fileName = `${testTableName}.csv`;
-    const downloadedFilePatch = join(__dirname, 'response-files', fileName);
+    const tmpDir = os.tmpdir();
+    const downloadedFilePatch = join(tmpDir, fileName);
 
-    const dir = join(__dirname, 'response-files');
-    if (!fs.existsSync(dir)) {
-      fs.mkdirSync(dir);
-    }
     // eslint-disable-next-line security/detect-non-literal-fs-filename
     fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body);
 
@@ -3653,12 +3647,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) =
   }
   t.is(getTableCsvResponse.status, 201);
   const fileName = `${testTableName}.csv`;
-  const downloadedFilePatch = join(__dirname, 'response-files', fileName);
+  const downloadedFilePatch = join(os.tmpdir(), fileName);
 
-  const dir = join(__dirname, 'response-files');
-  if (!fs.existsSync(dir)) {
-    fs.mkdirSync(dir);
-  }
   // eslint-disable-next-line security/detect-non-literal-fs-filename
   fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body);
   // eslint-disable-next-line security/detect-non-literal-fs-filename
@@ -3762,12 +3752,8 @@ test.serial(`${currentTest} should throw exception whe csv import is disabled`,
   }
   t.is(getTableCsvResponse.status, 201);
   const fileName = `${testTableName}.csv`;
-  const downloadedFilePatch = join(__dirname, 'response-files', fileName);
+  const downloadedFilePatch = join(os.tmpdir(), fileName);
 
-  const dir = join(__dirname, 'response-files');
-  if (!fs.existsSync(dir)) {
-    fs.mkdirSync(dir);
-  }
   // eslint-disable-next-line security/detect-non-literal-fs-filename
   fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body);
   // eslint-disable-next-line security/detect-non-literal-fs-filename
diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-ibmdb2-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-ibmdb2-e2e.test.ts
index f5d83979..d084d3f2 100644
--- a/backend/test/ava-tests/non-saas-tests/non-saas-table-ibmdb2-e2e.test.ts
+++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-ibmdb2-e2e.test.ts
@@ -28,6 +28,7 @@ import { fileURLToPath } from 'url';
 import { join } from 'path';
 import { setSaasEnvVariable } from '../../utils/set-saas-env-variable.js';
 import { WinstonLogger } from '../../../src/entities/logging/winston-logger.js';
+import os from 'node:os';
 
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = path.dirname(__filename);
@@ -3409,12 +3410,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) =
   }
   t.is(getTableCsvResponse.status, 201);
   const fileName = `${testTableName}.csv`;
-  const downloadedFilePatch = join(__dirname, 'response-files', fileName);
+  const downloadedFilePatch = join(os.tmpdir(), fileName);
 
-  const dir = join(__dirname, 'response-files');
-  if (!fs.existsSync(dir)) {
-    fs.mkdirSync(dir);
-  }
   // eslint-disable-next-line security/detect-non-literal-fs-filename
   fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body);
 
@@ -3479,12 +3476,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`,
     }
     t.is(getTableCsvResponse.status, 201);
     const fileName = `${testTableName}.csv`;
-    const downloadedFilePatch = join(__dirname, 'response-files', fileName);
+    const downloadedFilePatch = join(os.tmpdir(), fileName);
 
-    const dir = join(__dirname, 'response-files');
-    if (!fs.existsSync(dir)) {
-      fs.mkdirSync(dir);
-    }
     // eslint-disable-next-line security/detect-non-literal-fs-filename
     fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body);
 
diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-mongodb-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-mongodb-e2e.test.ts
index efed5670..e733085f 100644
--- a/backend/test/ava-tests/non-saas-tests/non-saas-table-mongodb-e2e.test.ts
+++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-mongodb-e2e.test.ts
@@ -28,6 +28,7 @@ import { send } from 'process';
 import { setSaasEnvVariable } from '../../utils/set-saas-env-variable.js';
 import { Cacher } from '../../../src/helpers/cache/cacher.js';
 import { WinstonLogger } from '../../../src/entities/logging/winston-logger.js';
+import os from 'node:os';
 
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = path.dirname(__filename);
@@ -3517,12 +3518,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) =
   }
   t.is(getTableCsvResponse.status, 201);
   const fileName = `${testTableName}.csv`;
-  const downloadedFilePatch = join(__dirname, 'response-files', fileName);
+  const downloadedFilePatch = join(os.tmpdir(), fileName);
 
-  const dir = join(__dirname, 'response-files');
-  if (!fs.existsSync(dir)) {
-    fs.mkdirSync(dir);
-  }
   // eslint-disable-next-line security/detect-non-literal-fs-filename
   fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body);
 
@@ -3592,12 +3589,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`,
     }
     t.is(getTableCsvResponse.status, 201);
     const fileName = `${testTableName}.csv`;
-    const downloadedFilePatch = join(__dirname, 'response-files', fileName);
+    const downloadedFilePatch = join(os.tmpdir(), fileName);
 
-    const dir = join(__dirname, 'response-files');
-    if (!fs.existsSync(dir)) {
-      fs.mkdirSync(dir);
-    }
     // eslint-disable-next-line security/detect-non-literal-fs-filename
     fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body);
 
diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-redis-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-redis-e2e.test.ts
index 947405bd..679b792d 100644
--- a/backend/test/ava-tests/non-saas-tests/non-saas-table-redis-e2e.test.ts
+++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-redis-e2e.test.ts
@@ -26,6 +26,7 @@ import { getTestData } from '../../utils/get-test-data.js';
 import { registerUserAndReturnUserInfo } from '../../utils/register-user-and-return-user-info.js';
 import { TestUtils } from '../../utils/test.utils.js';
 import { setSaasEnvVariable } from '../../utils/set-saas-env-variable.js';
+import os from 'node:os';
 
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = path.dirname(__filename);
@@ -3517,12 +3518,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) =
   }
   t.is(getTableCsvResponse.status, 201);
   const fileName = `${testTableName}.csv`;
-  const downloadedFilePatch = join(__dirname, 'response-files', fileName);
+  const downloadedFilePatch = join(os.tmpdir(), fileName);
 
-  const dir = join(__dirname, 'response-files');
-  if (!fs.existsSync(dir)) {
-    fs.mkdirSync(dir);
-  }
   // eslint-disable-next-line security/detect-non-literal-fs-filename
   fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body);
 
@@ -3592,12 +3589,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`,
     }
     t.is(getTableCsvResponse.status, 201);
     const fileName = `${testTableName}.csv`;
-    const downloadedFilePatch = join(__dirname, 'response-files', fileName);
+    const downloadedFilePatch = join(os.tmpdir(), fileName);
 
-    const dir = join(__dirname, 'response-files');
-    if (!fs.existsSync(dir)) {
-      fs.mkdirSync(dir);
-    }
     // eslint-disable-next-line security/detect-non-literal-fs-filename
     fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body);
 
@@ -3662,12 +3655,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) =
   }
   t.is(getTableCsvResponse.status, 201);
   const fileName = `${testTableName}.csv`;
-  const downloadedFilePatch = join(__dirname, 'response-files', fileName);
+  const downloadedFilePatch = join(os.tmpdir(), fileName);
 
-  const dir = join(__dirname, 'response-files');
-  if (!fs.existsSync(dir)) {
-    fs.mkdirSync(dir);
-  }
   // eslint-disable-next-line security/detect-non-literal-fs-filename
   fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body);
   // eslint-disable-next-line security/detect-non-literal-fs-filename
diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-user-admin-permissions-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-user-admin-permissions-e2e.test.ts
index 098515fa..2556d873 100644
--- a/backend/test/ava-tests/non-saas-tests/non-saas-user-admin-permissions-e2e.test.ts
+++ b/backend/test/ava-tests/non-saas-tests/non-saas-user-admin-permissions-e2e.test.ts
@@ -3597,6 +3597,8 @@ test.serial(`${currentTest} should return array of table widgets for table`, asy
     const getTableWidgetsRO = JSON.parse(getTableWidgets.text);
     t.is(typeof getTableWidgetsRO, 'object');
     t.is(getTableWidgetsRO.length, 2);
+    getTableWidgetsRO.sort((a, b) => a.field_name.localeCompare(b.field_name))
+    newTableWidgets.sort((a, b) => a.field_name.localeCompare(b.field_name))
 
     t.is(getTableWidgetsRO[0].field_name, newTableWidgets[0].field_name);
     t.is(getTableWidgetsRO[0].widget_type, newTableWidgets[0].widget_type);
@@ -3611,6 +3613,8 @@ test.serial(`${currentTest} should return array of table widgets for table`, asy
     t.is(getTableStructureResponse.status, 200);
     const getTableStructureRO = JSON.parse(getTableStructureResponse.text);
     t.is(getTableStructureRO.hasOwnProperty('table_widgets'), true);
+    getTableStructureRO.table_widgets.sort((a, b) => a.field_name.localeCompare(b.field_name))
+
     t.is(getTableStructureRO.table_widgets.length, 2);
     t.is(getTableStructureRO.table_widgets[0].field_name, newTableWidgets[0].field_name);
     t.is(getTableStructureRO.table_widgets[1].widget_type, newTableWidgets[1].widget_type);
diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-user-with-table-only-permissions-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-user-with-table-only-permissions-e2e.test.ts
index 5d13dc82..91b79cb3 100644
--- a/backend/test/ava-tests/non-saas-tests/non-saas-user-with-table-only-permissions-e2e.test.ts
+++ b/backend/test/ava-tests/non-saas-tests/non-saas-user-with-table-only-permissions-e2e.test.ts
@@ -2727,6 +2727,8 @@ test.serial(`${currentTest} should return array of table widgets for table`, asy
     const getTableStructureRO = JSON.parse(getTableStructureResponse.text);
     t.is(getTableStructureRO.hasOwnProperty('table_widgets'), true);
     t.is(getTableStructureRO.table_widgets.length, 2);
+    getTableStructureRO.table_widgets.sort((a, b) => a.field_name.localeCompare(b.field_name))
+    newTableWidgets.sort((a, b) => a.field_name.localeCompare(b.field_name))
     t.is(getTableStructureRO.table_widgets[0].field_name, newTableWidgets[0].field_name);
     t.is(getTableStructureRO.table_widgets[1].widget_type, newTableWidgets[1].widget_type);
     t.is(compareTableWidgetsArrays(getTableStructureRO.table_widgets, newTableWidgets), true);
diff --git a/backend/test/mock.factory.ts b/backend/test/mock.factory.ts
index b718332c..42a8f349 100644
--- a/backend/test/mock.factory.ts
+++ b/backend/test/mock.factory.ts
@@ -294,7 +294,7 @@ export class MockFactory {
     const dto = new CreateConnectionDto() as any;
     dto.title = 'Test connection to Redis in Docker';
     dto.type = ConnectionTypesEnum.redis;
-    dto.host = 'redis-e2e-testing';
+    dto.host = 'test-redis-e2e-testing';
     dto.port = 6379;
     dto.password = 'SuperSecretRedisPassword';
     dto.ssh = false;
diff --git a/docker-compose.tst.yml b/docker-compose.tst.yml
index 938d0602..c2caf07e 100644
--- a/docker-compose.tst.yml
+++ b/docker-compose.tst.yml
@@ -1,4 +1,3 @@
-version: "3.9"
 services:
   backend:
     build:
@@ -6,9 +5,9 @@ services:
     ports:
       - 3000:3000
     env_file: ./backend/.development.env
+    environment:
+      DATABASE_URL: "postgres://postgres:abc123@postgres:5432/postgres"
     volumes:
-      - ./backend/dist:/app/dist
-      - ./backend/src:/app/src
       - ./backend/src/migrations:/app/src/migrations
     depends_on:
       - postgres
@@ -18,6 +17,8 @@ services:
       - test-oracle-e2e-testing
       - test-ibm-db2-e2e-testing
       - test-dynamodb-e2e-testing
+      - test-redis-e2e-testing
+      - test-cassandra-e2e-testing
     links:
       - postgres
       - testMySQL-e2e-testing
@@ -26,34 +27,44 @@ services:
       - test-oracle-e2e-testing
       - test-ibm-db2-e2e-testing
       - test-dynamodb-e2e-testing
+      - test-redis-e2e-testing
+      - test-cassandra-e2e-testing
     command: ["yarn", "start"]
   backend_test:
     build:
       context: .
     env_file: ./backend/.development.env
+    environment:
+      - "DATABASE_URL=postgres://postgres:abc123@postgres:5432/postgres"
+      - "EXTRA_ARGS=$EXTRA_ARGS"
     volumes:
-      - ./backend/dist:/app/dist
-      - ./backend/src:/app/src
       - ./backend/src/migrations:/app/src/migrations
     depends_on:
-      - postgres
-      - testMySQL-e2e-testing
-      - testPg-e2e-testing
-      - mssql-e2e-testing
-      - test-oracle-e2e-testing
-      - test-ibm-db2-e2e-testing
-      - test-mongo-e2e-testing
-      - test-dynamodb-e2e-testing
-    links:
-      - postgres
-      - testMySQL-e2e-testing
-      - testPg-e2e-testing
-      - mssql-e2e-testing
-      - test-oracle-e2e-testing
-      - test-ibm-db2-e2e-testing
-      - test-mongo-e2e-testing
-      - test-dynamodb-e2e-testing
-    command: ["/bin/sh", "-c", "sleep 300 && yarn test"]
+      postgres:
+        condition: service_healthy
+      testMySQL-e2e-testing:
+        condition: service_healthy
+      testPg-e2e-testing:
+        condition: service_healthy
+      mssql-e2e-testing:
+        condition: service_healthy
+      test-oracle-e2e-testing:
+        condition: service_healthy
+      test-ibm-db2-e2e-testing:
+        condition: service_healthy
+      test-mongo-e2e-testing:
+        condition: service_healthy
+      test-dynamodb-e2e-testing:
+        condition: service_healthy
+      test-redis-e2e-testing:
+        condition: service_healthy
+      test-cassandra-e2e-testing:
+        condition: service_healthy
+    command: ["/bin/sh", "-c", "yarn test-all $EXTRA_ARGS"]
+    develop:
+      watch:
+        - action: rebuild
+          path: ./backend/src
 
   testMySQL-e2e-testing:
     image: mysql:8.0.23
@@ -64,6 +75,12 @@ services:
     environment:
       MYSQL_ROOT_PASSWORD: 123
       MYSQL_DATABASE: testDB
+    healthcheck:
+      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p123"]
+      interval: 10s
+      timeout: 5s
+      retries: 10
+      start_period: 30s
 
   testPg-e2e-testing:
     image: postgres
@@ -72,6 +89,12 @@ services:
     environment:
       POSTGRES_PASSWORD: 123
     command: postgres -c 'max_connections=300'
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U postgres"]
+      interval: 10s
+      timeout: 5s
+      retries: 10
+      start_period: 10s
 
   test-mongo-e2e-testing:
     image: mongo
@@ -81,6 +104,12 @@ services:
       MONGO_INITDB_ROOT_PASSWORD: example
     ports:
       - 27017:27017
+    healthcheck:
+      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
+      interval: 10s
+      timeout: 5s
+      retries: 10
+      start_period: 30s
 
   postgres:
     image: postgres
@@ -89,6 +118,14 @@ services:
     environment:
       POSTGRES_PASSWORD: abc123
     command: postgres -c 'max_connections=300'
+    tmpfs:
+      - /var/lib/postgresql
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U postgres"]
+      interval: 10s
+      timeout: 5s
+      retries: 10
+      start_period: 10s
 
   mssql-e2e-testing:
     image: mcr.microsoft.com/mssql/server:2019-latest
@@ -97,6 +134,12 @@ services:
       - ACCEPT_EULA=Y
     ports:
       - "5434:1433"
+    healthcheck:
+      test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'yNuXf@6T#BgoQ%U6knMp' -C -Q 'SELECT 1' || /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'yNuXf@6T#BgoQ%U6knMp' -Q 'SELECT 1'"]
+      interval: 10s
+      timeout: 5s
+      retries: 20
+      start_period: 60s
 
   test-oracle-e2e-testing:
     image: gvenzl/oracle-xe
@@ -104,6 +147,12 @@ services:
       - 1521:1521
     environment:
       ORACLE_PASSWORD: 12345
+    healthcheck:
+      test: ["CMD-SHELL", "healthcheck.sh"]
+      interval: 10s
+      timeout: 5s
+      retries: 30
+      start_period: 120s
 
   autoadmin-ws-server:
     build:
@@ -118,6 +167,38 @@ services:
       - backend
     depends_on:
       - backend
+  test-cassandra-e2e-testing:
+    image: cassandra:5.0.4
+    environment:
+      - CASSANDRA_CLUSTER_NAME=TestCluster
+      - CASSANDRA_DC=TestDC
+      - CASSANDRA_RACK=TestRack
+    restart: always
+    healthcheck:
+      test:
+        [
+          "CMD",
+          "cqlsh",
+          "-u",
+          "cassandra",
+          "-p",
+          "cassandra",
+          "-e",
+          "describe keyspaces",
+        ]
+      interval: 30s
+      timeout: 10s
+      retries: 5
+      start_period: 60s
+
+  test-redis-e2e-testing:
+    image: redis:7.0.11
+    command: ["redis-server", "--requirepass", "SuperSecretRedisPassword"]
+    healthcheck:
+      test: ["CMD", "redis-cli", "ping"]
+      interval: 30s
+      timeout: 10s
+      retries: 3
 
   # rocketadmin-agent_oracle:
   #   build:
@@ -232,11 +313,21 @@ services:
       - ETCD_PASSWORD=
     ports:
       - 50000:50000
+    healthcheck:
+      test: ["CMD-SHELL", "su - db2inst1 -c 'db2 connect to testdb'"]
+      interval: 30s
+      timeout: 30s
+      retries: 20
+      start_period: 180s
 
   test-dynamodb-e2e-testing:
     image: amazon/dynamodb-local
-    ports:
-      - 8000:8000
     environment:
       - AWS_ACCESS_KEY_ID=SuperSecretAwsAccessKey
       - AWS_SECRET=SuperSecretAwsSecret
+    healthcheck:
+      test: ["CMD-SHELL", "curl -s http://localhost:8000 || exit 1"]
+      interval: 10s
+      timeout: 5s
+      retries: 10
+      start_period: 10s
diff --git a/justfile b/justfile
index de52e37d..c4604474 100644
--- a/justfile
+++ b/justfile
@@ -1,2 +1,2 @@
-test:
-  docker compose  -f docker-compose.tst.yml up --abort-on-container-exit --force-recreate --build
+test args='test/ava-tests/non-saas-tests/*':
+  EXTRA_ARGS="{{args}}" docker compose  -f docker-compose.tst.yml up --abort-on-container-exit --force-recreate --build --attach=backend_test --no-log-prefix
diff --git a/rocketadmin-agent/tsconfig.build.json b/rocketadmin-agent/tsconfig.build.json
index 64f86c6b..79548c6d 100644
--- a/rocketadmin-agent/tsconfig.build.json
+++ b/rocketadmin-agent/tsconfig.build.json
@@ -1,4 +1,4 @@
 {
   "extends": "./tsconfig.json",
-  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
+  "exclude": ["node_modules", "dist", "**/*spec.ts"]
 }