Compare commits

...

1 Commits

Author SHA1 Message Date
Arunavo Ray
fcc92b8f27 added security audit 2025-11-08 07:49:33 +05:30
2 changed files with 496 additions and 0 deletions

496
Security-Audit.md Normal file
View File

@@ -0,0 +1,496 @@
Date: 2025-11-07Application: Gitea Mirror v3.9.0Branch: bun-v1.3.1Scope: Full application security review
---
Executive Summary
A comprehensive security audit was conducted across all components of the Gitea Mirror application, including authentication, authorization,
encryption, input validation, API endpoints, database operations, UI components, and external integrations. The review identified 23 security
findings across multiple severity levels.
Critical Findings: 2
High Severity: 9
Medium Severity: 10
Low Severity: 2
Overall Security Posture: The application demonstrates strong foundational security practices (AES-256-GCM encryption, Drizzle ORM parameterized
queries, React auto-escaping) but has critical authorization flaws that require immediate remediation.
---
CRITICAL SEVERITY VULNERABILITIES
Vuln 1: Broken Object Level Authorization (BOLA) - Multiple Endpoints
Severity: CRITICALConfidence: 1.0Category: Authorization Bypass
Affected Files:
- src/pages/api/config/index.ts:18
- src/pages/api/job/mirror-repo.ts:16
- src/pages/api/github/repositories.ts:11
- src/pages/api/dashboard/index.ts:9
- src/pages/api/activities/index.ts:8
- src/pages/api/sync/repository.ts:15
- src/pages/api/sync/organization.ts:14
- src/pages/api/job/mirror-org.ts:14
- src/pages/api/job/retry-repo.ts:18
- src/pages/api/job/sync-repo.ts:11
- src/pages/api/job/schedule-sync-repo.ts:13
Description: Multiple API endpoints accept a userId parameter from client requests without validating that the authenticated user matches the
requested userId. This allows any authenticated user to access and manipulate any other user's data.
Exploit Scenario:
POST /api/config/index
Content-Type: application/json
Cookie: better-auth-session=attacker-session
{
"userId": "victim-user-id",
"giteaConfig": {
"token": "attacker-controlled-token"
}
}
Result: Attacker modifies victim's configuration, steals encrypted tokens, triggers mirror operations, or accesses activity logs.
Recommendation:
export const POST: APIRoute = async (context) => {
// 1. Validate authentication
const { user, response } = await requireAuth(context);
if (response) return response;
// 2. Use authenticated userId from session, NOT from request body
const authenticatedUserId = user!.id;
// 3. Don't accept userId from client
const body = await request.json();
const { githubConfig, giteaConfig } = body;
// 4. Use session userId for all operations
const config = await db
.select()
.from(configs)
.where(eq(configs.userId, authenticatedUserId));
}
---
Vuln 2: Header Authentication Spoofing - src/lib/auth-header.ts:48
Severity: CRITICALConfidence: 0.95Category: Authentication Bypass
Description: The header authentication mechanism trusts HTTP headers (X-Authentik-Username, X-Authentik-Email) without proper validation. If the
application is accessible without a properly configured reverse proxy, attackers can send these headers directly to impersonate any user.
Exploit Scenario:
GET /api/config/index?userId=admin
Host: gitea-mirror.example.com
X-Authentik-Username: admin
X-Authentik-Email: admin@example.com
Result: Attacker gains admin access without authentication.
Recommendation:
// 1. Add trusted proxy IP validation
const TRUSTED_PROXY_IPS = process.env.TRUSTED_PROXY_IPS?.split(',') || [];
export function extractUserFromHeaders(headers: Headers, remoteIp: string): UserInfo | null {
// Validate request comes from trusted reverse proxy
if (TRUSTED_PROXY_IPS.length > 0 && !TRUSTED_PROXY_IPS.includes(remoteIp)) {
console.warn(`Header auth rejected: untrusted IP ${remoteIp}`);
return null;
}
// 2. Add shared secret validation
const authSecret = headers.get('X-Auth-Secret');
if (authSecret !== process.env.HEADER_AUTH_SECRET) {
console.warn('Header auth rejected: invalid secret');
return null;
}
// Continue with header extraction...
}
Additional Requirements:
- Document that header auth MUST only be used behind a reverse proxy
- Reverse proxy MUST strip X-Authentik-* headers from untrusted requests
- Direct access MUST be blocked via firewall rules
---
HIGH SEVERITY VULNERABILITIES
Vuln 3: Missing Authentication on Critical Endpoints
Severity: HIGHConfidence: 1.0Category: Authentication Bypass
Affected Endpoints:
- /api/github/test-connection - Tests GitHub connection with user's token
- /api/gitea/test-connection - Tests Gitea connection with user's token
- /api/rate-limit/index - Exposes rate limit info
- /api/events/index - SSE endpoint for events
- /api/activities/cleanup - Deletes activities
Description: Several sensitive endpoints lack authentication checks entirely.
Exploit Scenario:
POST /api/github/test-connection
{"token": "stolen-token", "userId": "victim"}
Recommendation: Add requireAuth to all sensitive endpoints.
---
Vuln 4: Token Exposure Through Config API - src/pages/api/config/index.ts:220
Severity: HIGHConfidence: 0.9Category: Sensitive Data Exposure
Description: The GET /api/config/index endpoint decrypts and returns GitHub/Gitea API tokens in plaintext. Combined with BOLA, any user can retrieve
any other user's tokens.
Exploit Scenario:
1. Attacker authenticates as User A
2. Calls /api/config/index?userId=admin
3. Receives admin's decrypted GitHub Personal Access Token
4. Uses stolen token to access admin's repositories
Recommendation:
// Never send full tokens - use masked versions
if (githubConfig.token) {
const decrypted = decrypt(githubConfig.token);
githubConfig.token = maskToken(decrypted); // "ghp_****...last4"
githubConfig.tokenSet = true;
}
---
Vuln 5: Static Salt in Key Derivation - src/lib/utils/encryption.ts:21
Severity: HIGHConfidence: 0.95Category: Cryptographic Weakness
Description: The key derivation function uses a static, hardcoded salt that reduces protection against precomputation attacks.
Exploit Scenario: If ENCRYPTION_SECRET is weak or compromised, the static salt makes brute-force attacks more efficient. Attackers can build targeted
rainbow tables.
Recommendation:
// Generate and store unique salt per installation
const SALT_FILE = '/app/data/.encryption_salt';
let installationSalt: Buffer;
if (fs.existsSync(SALT_FILE)) {
installationSalt = Buffer.from(fs.readFileSync(SALT_FILE, 'utf8'), 'hex');
} else {
installationSalt = crypto.randomBytes(32);
fs.writeFileSync(SALT_FILE, installationSalt.toString('hex'));
fs.chmodSync(SALT_FILE, 0o600);
}
return crypto.pbkdf2Sync(secret, installationSalt, ITERATIONS, KEY_LENGTH, 'sha256');
---
Vuln 6: Weak Default Secret - src/lib/config.ts:22
Severity: HIGHConfidence: 0.90Category: Hardcoded Credentials
Description: The application uses a hardcoded default for BETTER_AUTH_SECRET when not set.
Exploit Scenario: Non-Docker deployments may use the weak default. Attackers can forge authentication tokens using the known default secret.
Recommendation:
BETTER_AUTH_SECRET: (() => {
const secret = process.env.BETTER_AUTH_SECRET;
const weakDefaults = ["your-secret-key-change-this-in-production"];
if (!secret || weakDefaults.includes(secret)) {
if (process.env.NODE_ENV === 'production') {
throw new Error("BETTER_AUTH_SECRET required. Generate with: openssl rand -base64 32");
}
return "dev-secret-minimum-32-chars";
}
if (secret.length < 32) {
throw new Error("BETTER_AUTH_SECRET must be at least 32 characters");
}
return secret;
})()
---
Vuln 7: Unencrypted OAuth Client Secrets - src/lib/db/schema.ts:569
Severity: HIGHConfidence: 0.92Category: Sensitive Data Exposure
Description: OAuth application client secrets and SSO provider credentials are stored as plaintext, inconsistent with encrypted GitHub/Gitea tokens.
Exploit Scenario: Database leak exposes OAuth client secrets, allowing attackers to impersonate the application to external OAuth providers.
Recommendation:
// Encrypt before storage
import { encrypt } from "@/lib/utils/encryption";
await db.insert(oauthApplications).values({
clientSecret: encrypt(clientSecret),
// ...
});
// Add decryption helper
export function decryptOAuthClientSecret(encrypted: string): string {
return decrypt(encrypted);
}
---
Vuln 8: SSRF in OIDC Discovery - src/pages/api/sso/discover.ts:48
Severity: HIGHConfidence: 0.95Category: Server-Side Request Forgery
Description: The endpoint accepts user-provided issuer URL and fetches ${issuer}/.well-known/openid-configuration without validating against internal
networks.
Exploit Scenario:
POST /api/sso/discover
{"issuer": "http://169.254.169.254/latest/meta-data"}
Result: Access to AWS metadata service, internal APIs, or port scanning.
Recommendation:
// Block private IPs and localhost
const ALLOWED_PROTOCOLS = ['https:'];
const hostname = parsedIssuer.hostname.toLowerCase();
if (
!ALLOWED_PROTOCOLS.includes(parsedIssuer.protocol) ||
hostname === 'localhost' ||
hostname.match(/^10\./) ||
hostname.match(/^192\.168\./) ||
hostname.match(/^172\.(1[6-9]|2[0-9]|3[0-1])\./) ||
hostname.match(/^169\.254\./) ||
hostname.match(/^127\./)
) {
return new Response(JSON.stringify({ error: "Invalid issuer" }), { status: 400 });
}
---
Vuln 9: SSRF in Gitea Connection Test - src/pages/api/gitea/test-connection.ts:29
Severity: HIGHConfidence: 0.95Category: SSRF / Input Validation
Description: Accepts user-provided url and makes HTTP request without validation.
Exploit Scenario: Same as Vuln 8 - internal network scanning, metadata service access.
Recommendation: Apply same URL validation as Vuln 8.
---
Vuln 10: Command Injection in Docker Build - scripts/build-docker.sh:72
Severity: HIGHConfidence: 0.95Category: Command Injection
Description: Uses eval with dynamically constructed Docker command including environment variables.
Exploit Scenario:
DOCKER_IMAGE='test; rm -rf /; #'
./scripts/build-docker.sh
Recommendation:
# Replace eval with direct execution
if docker buildx build --platform linux/amd64,linux/arm64 \
-t "$FULL_IMAGE_NAME" \
${LOAD:+--load} \
${PUSH:+--push} \
.; then
echo "Success"
fi
---
Vuln 11: Path Traversal in Gitea API - src/lib/gitea.ts:193
Severity: MEDIUM-HIGHConfidence: 0.85Category: Path Traversal / SSRF
Description: Repository owner/name interpolated into URLs without encoding.
Exploit Scenario:
owner = "../../admin"
repoName = "users/../../../config"
// Results in: /api/v1/repos/../../admin/users/../../../config
Recommendation:
const response = await fetch(
`${config.url}/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repoName)}`,
// ...
);
Apply to all URL constructions in gitea.ts and gitea-enhanced.ts.
---
MEDIUM SEVERITY VULNERABILITIES
Vuln 12: Weak Session Configuration - src/lib/auth.ts:105
Severity: MEDIUMConfidence: 0.85
Description: 30-day session expiration is excessive; missing explicit security flags.
Recommendation:
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days max
cookieOptions: {
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
httpOnly: true,
},
}
---
Vuln 13: Missing CSRF Protection
Severity: MEDIUMConfidence: 0.80
Description: No visible CSRF token validation on state-changing operations.
Recommendation: Verify Better Auth CSRF protection is enabled; implement explicit tokens if needed.
---
Vuln 14: Weak Random Number Generation - src/lib/utils.ts:12
Severity: MEDIUMConfidence: 0.85
Description: generateRandomString() uses Math.random() (not cryptographically secure) for OAuth client credentials.
Recommendation:
// Use existing secure function
const clientId = `client_${generateSecureToken(16)}`;
const clientSecret = `secret_${generateSecureToken(24)}`;
---
Vuln 15-18: Input Validation Issues
Severity: MEDIUMConfidence: 0.85
Affected:
- src/pages/api/repositories/[id].ts:24 - destinationOrg not validated
- src/pages/api/organizations/[id].ts:24 - destinationOrg not validated
- src/pages/api/job/mirror-repo.ts:19 - repositoryIds array not validated
- src/lib/helpers.ts:49 - Repository names in messages not sanitized
Recommendation: Use Zod schemas for all user inputs:
const updateRepoSchema = z.object({
destinationOrg: z.string()
.min(1)
.max(100)
.regex(/^[a-zA-Z0-9\-_\.]+$/)
.nullable()
.optional()
});
---
LOW SEVERITY VULNERABILITIES
Vuln 19: XSS via Astro set:html - src/pages/docs/quickstart.astro:102
Severity: LOWConfidence: 0.85
Description: Uses set:html with static data; risky pattern if copied with dynamic content.
Recommendation:
<p class="text-sm" set:text={item.text}></p>
---
Vuln 20: Open Redirect in OAuth - src/components/oauth/ConsentPage.tsx:118
Severity: LOWConfidence: 0.90
Description: OAuth redirect uses window.location.href without explicit protocol validation.
Recommendation:
if (!['http:', 'https:'].includes(url.protocol)) {
throw new Error('Invalid protocol');
}
window.location.assign(url.toString());
---
POSITIVE SECURITY FINDINGS ✅
The codebase demonstrates excellent security practices:
1. SQL Injection Protection - Drizzle ORM with parameterized queries throughout
2. Token Encryption - AES-256-GCM for GitHub/Gitea tokens
3. XSS Protection - React auto-escaping, no dangerouslySetInnerHTML found
4. Password Hashing - bcrypt via Better Auth
5. Secure Random - crypto.randomBytes() in encryption functions
6. Input Validation - Comprehensive Zod schemas
7. Safe Path Operations - path.join() with proper base directories
8. No Command Execution - TypeScript codebase doesn't use child_process
9. Authentication Framework - Better Auth with session management
10. Type Safety - TypeScript strict mode
---
REMEDIATION PRIORITY
Phase 1: EMERGENCY (Deploy Today)
1. Fix BOLA vulnerabilities - Add authentication and userId validation (Vuln 1)
2. Disable or secure header auth - Add IP whitelist validation (Vuln 2)
3. Add authentication checks - Protect unprotected endpoints (Vuln 3)
Phase 2: URGENT (This Week)
1. Implement token masking - Don't return decrypted tokens (Vuln 4)
2. Fix SSRF vulnerabilities - Add URL validation (Vuln 8, 9)
3. Fix command injection - Remove eval from build script (Vuln 10)
4. Add URL encoding - Path traversal fix (Vuln 11)
Phase 3: HIGH (This Sprint)
1. Fix KDF salt - Generate unique salt per installation (Vuln 5)
2. Enforce strong secrets - Fail if weak defaults used (Vuln 6)
3. Encrypt OAuth secrets - Match GitHub/Gitea token encryption (Vuln 7)
4. Reduce session expiration - 7 days maximum (Vuln 12)
5. Add CSRF protection - Explicit tokens (Vuln 13)
Phase 4: MEDIUM (Next Sprint)
1. Input validation - Zod schemas for all endpoints (Vuln 14-18)
2. Fix weak RNG - Use crypto.randomBytes (Vuln 14)
Phase 5: LOW (Backlog)
1. Remove set:html - Use safer alternatives (Vuln 19)
2. OAuth redirect validation - Add protocol check (Vuln 20)
---
TESTING RECOMMENDATIONS
Proof of Concept Tests
# Test BOLA
curl -X POST http://localhost:4321/api/config/index \
-H "Content-Type: application/json" \
-H "Cookie: better-auth-session=<attacker-session>" \
-d '{"userId":"victim-id","githubConfig":{...}}'
# Test header spoofing
curl http://localhost:4321/api/config/index?userId=admin \
-H "X-Authentik-Username: admin" \
-H "X-Authentik-Email: admin@example.com"
# Test SSRF
curl -X POST http://localhost:4321/api/sso/discover \
-H "Content-Type: application/json" \
-d '{"issuer":"http://169.254.169.254/latest/meta-data"}'
---
SECURITY GRADE
Overall Security Grade: C+
- Would be B+ after Phase 1-2 fixes
- Would be A- after all fixes
Critical Issues: 2 (authorization bypass, auth spoofing)Risk Level: HIGH due to BOLA allowing complete account takeover
---
CONCLUSION
The Gitea Mirror application has a strong security foundation with proper encryption, ORM usage, and XSS protection. However, critical authorization
flaws in API endpoints allow authenticated users to access any other user's data. These must be fixed immediately before production deployment.
The development team has clearly prioritized security (encryption, input validation, type safety), and the identified issues are fixable with
moderate effort. After remediation, this will be a secure, production-ready application.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

After

Width:  |  Height:  |  Size: 330 KiB