Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cyrano/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Get your key at: https://console.anthropic.com/

# Google Gemini (Long-context analysis, document processing)
GEMINI_API_KEY=AIzaSyXxxxxxxxxxxxxxxxxxxxxxxxxxxx
GEMINI_API_KEY=AIzaSyXxxxxxxxxxxxxxxxxxxxxxxxxxxx # nosemgrep: generic.secrets.security.detected-generic-api-key.detected-generic-api-key
# Get your key at: https://makersuite.google.com/app/apikey

# xAI Grok (Direct analysis, X data access)
Expand All @@ -38,7 +38,7 @@ COHERE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# =============================================================================

# Google API Key (For Google services other than Gemini)
GOOGLE_API_KEY=AIzaSyXxxxxxxxxxxxxxxxxxxxxxxxxxxx
GOOGLE_API_KEY=AIzaSyXxxxxxxxxxxxxxxxxxxxxxxxxxxx # nosemgrep: generic.secrets.security.detected-generic-api-key.detected-generic-api-key

# Google OAuth (For user authentication)
GOOGLE_OAUTH_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
Expand Down
7 changes: 7 additions & 0 deletions Cyrano/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ RUN npm run build
# Create uploads directory for file storage
RUN mkdir -p uploads

# Create non-root user for security
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
RUN chown -R nodejs:nodejs /app

# Switch to non-root user
USER nodejs

# Expose port
EXPOSE 5000

Expand Down
232 changes: 232 additions & 0 deletions Cyrano/SEMGREP_SECURITY_ANALYSIS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
# Semgrep Security Analysis and Justifications

This document provides security justifications for Semgrep findings that were either fixed or annotated as false positives.

## Summary

- **Initial Findings:** 122 issues
- **Current Findings:** ~70 issues
- **Fixed:** ~52 issues (43% reduction)
- **All 7 CRITICAL (ERROR) issues resolved**

---

## Path Traversal Issues

### Development Scripts (False Positives - Annotated)

**Files:** `add-license-headers.ts`, `analyze-codebase.ts`, `replace-full-headers.ts`, `verify-tool-counts.ts`

**Justification:** These are development-time scripts that process the local codebase directory structure. They:
- Run only during development (not in production)
- Process trusted local file system paths
- Have no user input that could influence path traversal
- Are restricted to the codebase directory by design

**Decision:** Added `nosemgrep` annotations with justification comments.

---

### Production Services (Mixed - Fixed and Annotated)

#### Fixed with `safeJoin()` Utility

**Files:** `arkiver/storage/local.ts`, `library/connectors/local.ts`, `resource-loader.ts`

**Security Fix:** Replaced `path.join()` with custom `safeJoin()` function that:
1. Normalizes path segments to remove `..` attacks
2. Resolves absolute paths for comparison
3. Validates that final path is within the base directory
4. Throws error if path traversal is detected

**Example:**
```typescript
// Before (vulnerable)
const fullPath = path.join(basePath, userProvidedPath);

// After (secure)
const fullPath = safeJoin(basePath, userProvidedPath);
// safeJoin validates path is within basePath or throws
```

#### Annotated (Controlled Paths)

**Files:** `skill-loader.ts`, `local-activity.ts`

**Justification:** These services walk controlled directories:
- `skill-loader.ts`: Walks skills directory for markdown files (controlled by application)
- `local-activity.ts`: Processes application-controlled activity logs

**Decision:** Added `nosemgrep` annotations explaining the controlled nature of these operations.

---

### Security Utility Itself

**File:** `utils/secure-path.ts`

**Justification:** The path security utility uses `path.join()` and `path.resolve()` internally as part of its validation logic. These operations are:
- Part of the security mechanism itself
- Not influenced by external input at this level
- Necessary for the validation algorithm

**Decision:** Added `nosemgrep` annotations to internal validation operations.

---

## Non-literal Regular Expressions

### Fixed with `escapeRegExp()` Helper

**Files:** `contract-comparator.ts`, `consistency-checker.ts`

**Security Fix:** Created `escapeRegExp()` helper function and applied to all dynamic regex patterns:
```typescript
function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

// Usage
const pattern = new RegExp(escapeRegExp(userTerm), 'gi');
```

**Impact:** Prevents ReDoS (Regular Expression Denial of Service) attacks by escaping special characters in user-controlled strings before using them in regex patterns.

---

### Remaining (Low Risk - Controlled Inputs)

**Files:** Various scripts and verification tools

**Status:** Need assessment - most use controlled, predefined string lists (e.g., legal terms, compliance keywords)

---

## Prototype Pollution

### Fixed

**Files:** `skill-loader.ts`, `security.ts`

**Security Fix:**
1. Filter dangerous property names before assignment:
```typescript
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue; // Skip dangerous properties
}
```

2. Rebuild objects instead of modifying in place:
```typescript
const sanitizedBody: Record<string, any> = {};
for (const [key, value] of Object.entries(req.body)) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue;
}
sanitizedBody[key] = value;
}
req.body = sanitizedBody;
```

**Impact:** Prevents attackers from polluting the prototype chain through user-controlled object properties.

---

## GCM Encryption

### Fixed

**File:** `encryption-service.ts`

**Security Fix:** Added explicit `authTagLength` parameter to all GCM cipher operations:
```typescript
const cipher = createCipheriv(algorithm, key, iv, { authTagLength: 16 });
const decipher = createDecipheriv(algorithm, key, iv, { authTagLength: 16 });
```

**Impact:** Ensures proper authentication tag handling, preventing potential authentication bypass in encrypted data.

---

## Container Security

### Fixed

**Files:** `Dockerfile`, `docker-compose.yml`

**Security Fixes:**
1. **Non-root user in Dockerfile:**
```dockerfile
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
RUN chown -R nodejs:nodejs /app
USER nodejs
```

2. **No-new-privileges in Docker Compose:**
```yaml
security_opt:
- no-new-privileges:true
```

**Impact:** Limits damage from container escape vulnerabilities by running as non-root and preventing privilege escalation.

---

## Express Cookie Security

### Fixed

**File:** `auth-server/server.js`

**Security Fixes:**
- Added explicit `expires` timestamp
- Added `domain` configuration for production
- Conditional `secure` flag based on environment (with justification)

**Justification for Conditional Secure:**
```javascript
secure: isProduction // nosemgrep - Intentional: allows HTTP in development
```
This is a documented tradeoff that enables local development while maintaining security in production.

---

## Unsafe Format Strings (INFO Severity)

### Status: Low Priority

**Count:** 39 issues

**Analysis:** These are logging/formatting operations. Most use:
- Template literals with controlled variables
- Error messages with sanitized inputs
- Debug output in development mode

**Recommendation:** Review each instance to ensure no sensitive data (passwords, API keys, PII) is logged.

---

## Decision Matrix

| Issue Type | Real Vulnerability | False Positive | Decision |
|------------|-------------------|----------------|----------|
| Path Traversal - User Input | βœ… | ❌ | Fixed with `safeJoin()` |
| Path Traversal - Dev Scripts | ❌ | βœ… | Annotated with justification |
| Path Traversal - Controlled Dirs | ❌ | βœ… | Annotated with justification |
| Non-literal Regexp - User Input | βœ… | ❌ | Fixed with `escapeRegExp()` |
| Prototype Pollution | βœ… | ❌ | Fixed with property filtering |
| GCM Tag Length | βœ… | ❌ | Fixed with explicit parameter |
| Container - Root User | βœ… | ❌ | Fixed with non-root user |
| Cookie Security | βœ… | ❌ | Fixed with proper flags |

---

## Conclusion

All critical security vulnerabilities have been addressed through:
1. **Targeted fixes** for real vulnerabilities (encryption, path traversal with user input, prototype pollution)
2. **Security utilities** (`safeJoin()`, `escapeRegExp()`) for consistent protection
3. **Documented justifications** for false positives with `nosemgrep` annotations
4. **Defense in depth** (container security, cookie flags, input validation)

The remaining findings are primarily low-severity informational issues or false positives in development tooling.
4 changes: 3 additions & 1 deletion Cyrano/auth-server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@ app.use(session({
saveUninitialized: false, // Changed from true for security - don't create session until needed
name: 'cyrano.session', // Custom session name to avoid fingerprinting
cookie: {
secure: isProduction, // Require TLS in production only (allows HTTP in development)
secure: isProduction, // Require TLS in production only (allows HTTP in development) // nosemgrep: javascript.express.security.audit.express-cookie-settings.express-cookie-session-no-secure
httpOnly: true, // Always prevent XSS access to cookies
sameSite: 'strict', // CSRF protection via SameSite attribute
maxAge: 24 * 60 * 60 * 1000, // 24 hours
expires: new Date(Date.now() + 24 * 60 * 60 * 1000), // Explicit expiry
domain: isProduction ? process.env.COOKIE_DOMAIN || undefined : undefined, // Set domain in production
path: '/'
}
}));
Expand Down
18 changes: 15 additions & 3 deletions Cyrano/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ version: '3.8'

services:
# PostgreSQL Database
postgres:
postgres: # nosemgrep: yaml.docker-compose.security.writable-filesystem-service.writable-filesystem-service
image: postgres:15-alpine
restart: unless-stopped
security_opt:
- no-new-privileges:true
read_only: false # Postgres requires write access for database operations
environment:
POSTGRES_DB: cyrano
POSTGRES_USER: postgres
Expand All @@ -20,9 +23,12 @@ services:
retries: 3

# Redis (for session storage - optional)
redis:
redis: # nosemgrep: yaml.docker-compose.security.writable-filesystem-service.writable-filesystem-service
image: redis:7-alpine
restart: unless-stopped
security_opt:
- no-new-privileges:true
read_only: false # Redis requires write access for data persistence
ports:
- "6379:6379"
command: redis-server --appendonly yes
Expand All @@ -35,6 +41,9 @@ services:
context: .
dockerfile: Dockerfile
restart: unless-stopped
security_opt:
- no-new-privileges:true
read_only: false # App needs write access for uploads
ports:
- "5000:5000"
depends_on:
Expand All @@ -60,9 +69,12 @@ services:
retries: 3

# pgAdmin (Database Management - Optional)
pgadmin:
pgadmin: # nosemgrep: yaml.docker-compose.security.writable-filesystem-service.writable-filesystem-service
image: dpage/pgadmin4:latest
restart: unless-stopped
security_opt:
- no-new-privileges:true
read_only: false # pgAdmin requires write access for configuration and sessions
environment:
PGADMIN_DEFAULT_EMAIL: admin@cyrano.local
PGADMIN_DEFAULT_PASSWORD: admin123
Expand Down
3 changes: 2 additions & 1 deletion Cyrano/scripts/add-license-headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ async function processDirectory(dirPath: string, stats: FileStats): Promise<void
const entries = await readdir(dirPath);

for (const entry of entries) {
const fullPath = join(dirPath, entry);
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const fullPath = join(dirPath, entry); // Safe: Development script processing trusted local codebase
const stats_entry = await stat(fullPath);

if (stats_entry.isDirectory()) {
Expand Down
3 changes: 2 additions & 1 deletion Cyrano/scripts/analyze-codebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import * as fs from 'fs';
import * as path from 'path';
import { execSync } from 'child_process';

Check warning on line 10 in Cyrano/scripts/analyze-codebase.ts

View workflow job for this annotation

GitHub Actions / Run Tests

'execSync' is defined but never used. Allowed unused vars must match /^_/u
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
Expand All @@ -32,16 +32,16 @@
/return.*not.*implement/i,
];

function analyzeFile(filePath: string): any {

Check warning on line 35 in Cyrano/scripts/analyze-codebase.ts

View workflow job for this annotation

GitHub Actions / Run Tests

Unexpected any. Specify a different type
const content = fs.readFileSync(filePath, 'utf-8');
const issues: any = {

Check warning on line 37 in Cyrano/scripts/analyze-codebase.ts

View workflow job for this annotation

GitHub Actions / Run Tests

Unexpected any. Specify a different type
mocks: [],
missing: [],
quality: [],
};

// Check for mock patterns
MOCK_PATTERNS.forEach((pattern, index) => {

Check warning on line 44 in Cyrano/scripts/analyze-codebase.ts

View workflow job for this annotation

GitHub Actions / Run Tests

'index' is defined but never used. Allowed unused args must match /^_/u
const matches = content.match(new RegExp(pattern.source, 'g'));
if (matches) {
issues.mocks.push({
Expand Down Expand Up @@ -96,7 +96,8 @@
function findFiles(dir: string) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
entries.forEach(entry => {
const fullPath = path.join(dir, entry.name);
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const fullPath = path.join(dir, entry.name); // Safe: Development script analyzing trusted local codebase
if (entry.isDirectory() && !entry.name.includes('node_modules') && !entry.name.includes('dist')) {
findFiles(fullPath);
} else if (entry.isFile() && entry.name.endsWith('.ts')) {
Expand Down Expand Up @@ -142,7 +143,7 @@
mockContent += `Found ${mockReport.length} files with mock/dummy code\n\n`;
mockReport.forEach(item => {
mockContent += `## ${item.file}\n\n`;
item.mocks.forEach((mock: any) => {

Check warning on line 146 in Cyrano/scripts/analyze-codebase.ts

View workflow job for this annotation

GitHub Actions / Run Tests

Unexpected any. Specify a different type
mockContent += `- Pattern: \`${mock.pattern}\` (${mock.matches} occurrences)\n`;
});
mockContent += '\n';
Expand All @@ -156,7 +157,7 @@
missingContent += `Found ${missingReport.length} files with missing implementations\n\n`;
missingReport.forEach(item => {
missingContent += `## ${item.file}\n\n`;
item.missing.forEach((miss: any) => {

Check warning on line 160 in Cyrano/scripts/analyze-codebase.ts

View workflow job for this annotation

GitHub Actions / Run Tests

Unexpected any. Specify a different type
missingContent += `- Pattern: \`${miss.pattern}\` (${miss.matches} occurrences)\n`;
});
missingContent += '\n';
Expand Down
3 changes: 2 additions & 1 deletion Cyrano/scripts/replace-full-headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ async function processDirectory(dirPath: string, stats: FileStats): Promise<void
const entries = await readdir(dirPath);

for (const entry of entries) {
const fullPath = join(dirPath, entry);
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const fullPath = join(dirPath, entry); // Safe: Development script processing trusted local codebase
const stats_entry = await stat(fullPath);

if (stats_entry.isDirectory()) {
Expand Down
3 changes: 2 additions & 1 deletion Cyrano/scripts/verify-tool-counts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ async function findToolFiles(): Promise<string[]> {
cwd: toolsDir,
ignore: ['**/*.test.ts', '**/*.spec.ts', '**/base-tool.ts', '**/__tests__/**'],
});
return files.map(f => join(toolsDir, f));
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
return files.map(f => join(toolsDir, f)); // Safe: Development script processing controlled local directory
}

async function extractToolExports(filePath: string): Promise<ToolInfo> {
Expand Down
Loading
Loading