Alexandria implements multiple layers of security to protect users and prevent common web vulnerabilities. Security is considered at every level of the application, from input validation to content sanitization.
All URL parameters are validated before processing to prevent injection attacks and ensure data integrity.
Rules:
- 1-39 characters
- Alphanumeric and hyphens only
- Cannot start or end with hyphen
- Single character must be alphanumeric
Implementation: src/utils/validationUtils.ts
export function isValidGitHubOwner(owner: string): boolean {
if (!owner || owner.length === 0 || owner.length > 39) {
return false
}
if (owner.length === 1) {
return /^[a-zA-Z0-9]$/.test(owner)
}
const regex = /^[a-zA-Z0-9]([a-zA-Z0-9-])*[a-zA-Z0-9]$/
return regex.test(owner)
}Rules:
- Alphanumeric, dots, underscores, hyphens
- Maximum 100 characters
- Cannot be empty
Implementation: src/utils/validationUtils.ts
export function isValidGitHubRepo(repo: string): boolean {
if (!repo || repo.length === 0 || repo.length > 100) {
return false
}
const regex = /^[a-zA-Z0-9._-]+$/
return regex.test(repo)
}Rules:
- Prevents path traversal (
..,//) - Prevents absolute paths (starting with
/) - Blocks dangerous characters (
<,>,:,|,?,*, control characters)
Implementation: src/utils/validationUtils.ts
export function isValidRepoPath(path: string): boolean {
if (!path || path.length === 0) {
return false
}
// Prevent path traversal
if (path.includes('..') || path.includes('//')) {
return false
}
// Prevent absolute paths
if (path.startsWith('/')) {
return false
}
// Block dangerous characters
const dangerousChars = /[<>:"|?*\x00-\x1f]/
if (dangerousChars.test(path)) {
return false
}
return true
}Rules:
- Alphanumeric, dots, underscores, hyphens, slashes
- Maximum 255 characters
- Prevents path traversal sequences
Implementation: src/utils/validationUtils.ts
export function isValidGitHubRef(ref: string): boolean {
if (!ref || ref.length === 0 || ref.length > 255) {
return false
}
// Prevent path traversal
if (ref.includes('..') || ref.includes('//')) {
return false
}
const regex = /^[a-zA-Z0-9._\-\/]+$/
return regex.test(ref)
}The system prevents path traversal attacks through multiple mechanisms:
- Validation: All paths validated before use
- Sanitization: Paths sanitized to remove dangerous sequences
- Resolution Limits: Relative path resolution prevents going above repository root
Implementation: src/services/LinkRewriter.ts
private resolveRelativePath(href: string, currentPath: string): string {
// ... resolution logic ...
// Prevent going above repository root
if (upCount > pathParts.length) {
resolvedPath = hrefParts.slice(upCount).join('/')
}
// Validate resolved path
if (!isValidRepoPath(resolvedPath)) {
return href // Return original if invalid
}
return resolvedPath
}All markdown content is sanitized before rendering to prevent XSS attacks.
Implementation: src/components/MarkdownRenderer/MarkdownRenderer.tsx
Only safe HTML tags are allowed:
- Headings:
h1,h2,h3,h4,h5,h6 - Text:
p,br,strong,em,del,ins - Lists:
ul,ol,li - Code:
pre,code - Tables:
table,thead,tbody,tr,th,td - Links:
a - Images:
img - Other:
blockquote,hr,div,span
The following dangerous tags are explicitly blocked:
script- Prevents JavaScript executioniframe- Prevents embedded contentobject- Prevents plugin executionembed- Prevents plugin executionform- Prevents form submissioninput- Prevents form input- Event handlers - All event attributes blocked
Attributes are restricted per tag:
attributes: {
'*': ['className', 'id'],
'a': ['href', 'target', 'rel', 'title'],
'img': ['src', 'alt', 'width', 'height', 'title'],
'div': ['align'],
'h1': ['id'],
// ... other headings with id
}Note: style attribute is explicitly excluded to prevent CSS injection attacks.
Only safe protocols are allowed:
protocols: {
'a': {
href: ['http', 'https', 'mailto']
},
'img': {
src: ['http', 'https', 'data'] // data: URLs for base64 images
}
}Blocked Protocols:
javascript:- Prevents script executionvbscript:- Prevents script executiondata:text/html- Prevents HTML injection via data URLs (except images)
The markdown processing pipeline includes:
- remark-gfm: Processes GitHub Flavored Markdown safely
- rehype-raw: Processes raw HTML (then sanitized)
- rehype-sanitize: Sanitizes HTML with whitelist
- rehype-highlight: Syntax highlighting (safe, no execution)
GitHub tokens are handled securely:
- Build-time Only: Tokens only available at build time via environment variables
- No Exposure: Tokens never logged or included in error messages
- Client Bundle: Only
VITE_prefixed variables included in bundle - Read-only: Tokens only need public read access (no write scopes)
Implementation: src/services/GitHubApiClient.ts
constructor(private cache?: CacheManager) {
const token = import.meta.env.VITE_GITHUB_TOKEN
if (token) {
this.defaultHeaders['Authorization'] = `token ${token}`
// Token is safe - only in build-time bundle
}
}All API requests include:
- Proper headers (User-Agent, API version)
- Input validation before requests
- Error handling without exposing sensitive data
- Rate limit protection
API responses are validated:
- Type checking with TypeScript
- Null/undefined checks
- Expected structure validation
- Error response handling
The system protects against rate limit exhaustion:
- Monitoring: Tracks rate limit state from response headers
- Cache Fallback: Serves cached content when rate limited
- User Feedback: Clear warnings about rate limit status
- Automatic Recovery: Navigates to cached content when possible
Implementation: src/services/GitHubApiClient.ts
private async makeRequest<T>(endpoint: string, options?: RequestInit): Promise<T> {
// Check rate limit before making request
if (this.rateLimitState.remaining === 0) {
// Try to serve from cache if available
const cacheKey = this.generateCacheKey(endpoint, options)
if (this.cache) {
const cached = await this.cache.get<T>(cacheKey)
if (cached) {
console.warn('Rate limited, serving from cache:', endpoint)
return cached
}
}
throw new RateLimitError(this.rateLimitState.reset)
}
// ... make request ...
}Handles GitHub's secondary rate limits (abuse detection):
if (response.status === 403 || response.status === 429) {
const retryAfter = response.headers.get('retry-after')
if (retryAfter) {
// Secondary rate limit with retry-after header
throw new SecondaryRateLimitError(parseInt(retryAfter, 10), response.status)
}
}- Cache keys are namespaced to prevent conflicts
- Reading history is isolated to application
- No sensitive data stored locally
- localStorage: Used for small items (< 100KB) and metadata
- IndexedDB: Used for large items (> 100KB)
- Reading history: Limited to 50 items
All cached data has TTL (Time To Live):
- Default: 6 hours
- Expired items automatically removed
- Prevents stale data accumulation
For deployments, consider adding Content Security Policy headers:
Content-Security-Policy: default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.github.com;
font-src 'self';
Note: unsafe-inline may be needed for Vite's HMR in development. Consider nonce-based CSP for production.
Multiple security layers:
- Input validation
- Content sanitization
- Path traversal prevention
- Rate limit protection
- Error handling without information leakage
- GitHub tokens only have read access
- No write or admin scopes
- Minimal required permissions
- Invalid input rejected
- Errors don't expose sensitive information
- Fallback to safe defaults
- Validate early
- Validate on client and server (if applicable)
- Sanitize before processing
- Validate output format
- Generic error messages for users
- Detailed errors only in development
- No stack traces in production
- No sensitive data in error messages
-
Client-Side Only: All security is client-side. Malicious users can bypass client-side checks, but this doesn't affect other users.
-
GitHub API Dependency: Security depends on GitHub API security. The application trusts GitHub API responses.
-
CORS: Relies on GitHub API CORS policy. GitHub allows requests from any origin for public endpoints.
-
Content Trust: Markdown content from repositories is trusted after sanitization. Repository owners control content.
-
HTTPS Only: Always deploy over HTTPS to prevent man-in-the-middle attacks.
-
CSP Headers: Implement Content Security Policy headers for additional protection.
-
Regular Updates: Keep dependencies updated to patch security vulnerabilities.
-
Security Audits: Regularly audit dependencies with
npm audit. -
Token Rotation: Rotate GitHub tokens periodically if used.
If you discover a security vulnerability:
- Do not open a public issue
- Contact the maintainer privately
- Provide detailed information about the vulnerability
- Allow time for fix before public disclosure
When adding new features:
- Validate all user input
- Sanitize all rendered content
- Prevent path traversal
- Handle errors securely
- Test with malicious input
- Review dependency security
- Update documentation
- Consider rate limit impact