Skip to content
Open
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
14 changes: 13 additions & 1 deletion .github/workflows/test_unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ jobs:

- name: Install dependencies
run: |
uv pip install --system pytest pytest-cov
uv pip install --system pytest pytest-cov pytest-asyncio pytest-mock
uv pip install --system beautifulsoup4
uv pip install --system -e ".[api]"

- name: Validate packaging integrity
Expand Down Expand Up @@ -120,6 +121,17 @@ jobs:
echo " - ASR: Automatic speech recognition utilities"
echo " - TTS: Text-to-speech utilities"
echo " - InitCommand: gaia init profiles and installer logic"
echo " - FileSystemIndex: Persistent file index with FTS5 search"
echo " - FileSystemToolsMixin: browse_directory, tree, file_info, find_files, read_file, bookmark tools"
echo " - ScratchpadService: SQLite working memory for data analysis"
echo " - ScratchpadToolsMixin: create_table, insert_data, query_data, list_tables, drop_table tools"
echo " - BrowserTools: WebClient SSRF prevention, HTML extraction, downloads"
echo " - WebClient Edge Cases: parse_html fallback, extract_text, tables, links, download redirects"
echo " - Categorizer: auto_categorize, category map completeness, extension uniqueness"
echo " - ChatAgent Integration: filesystem, scratchpad, browser init/config/cleanup"
echo " - File Write Guardrails: blocked dirs, sensitive files, size limits, backup, audit"
echo " - Security Edge Cases: symlinks, audit logging, TOCTOU, prompt_overwrite"
echo " - Service Edge Cases: DB corruption rebuild, shared DB, row limits, transaction atomicity"
echo ""
echo "Integration Tests:"
echo " - DatabaseMixin + Agent: Full agent lifecycle with database"
Expand Down
31 changes: 30 additions & 1 deletion docs/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,9 @@
const parsed = url.parse(target || '');
// Only redirect to relative paths (no host/protocol) to prevent open redirects
if (!parsed.host && !parsed.protocol && parsed.pathname) {
res.redirect(303, parsed.pathname);
// Sanitize pathname to prevent protocol-relative URLs (e.g., //evil.com)
const safePath = parsed.pathname.startsWith('/') && !parsed.pathname.startsWith('//') ? parsed.pathname : '/';
res.redirect(303, safePath);

Check warning

Code scanning / CodeQL

Server-side URL redirect Medium documentation

Untrusted URL redirection depends on a
user-provided value
.
Untrusted URL redirection depends on a
user-provided value
.
} else {
res.redirect(303, '/');
}
Expand All @@ -317,6 +319,33 @@
res.redirect('/');
});

// Simple in-memory rate limiter for general requests (no external dependencies)
const rateLimitStore = new Map();
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
const RATE_LIMIT_MAX = 100; // max requests per window

function rateLimiter(req, res, next) {
const ip = req.ip || req.connection.remoteAddress;
const now = Date.now();
const record = rateLimitStore.get(ip) || { count: 0, resetAt: now + RATE_LIMIT_WINDOW };

if (now > record.resetAt) {
record.count = 0;
record.resetAt = now + RATE_LIMIT_WINDOW;
}

record.count++;
rateLimitStore.set(ip, record);

if (record.count > RATE_LIMIT_MAX) {
return res.status(429).send('Too Many Requests');
}
next();
}

// Apply rate limiter before auth middleware
app.use(rateLimiter);

// Apply auth middleware
app.use(authMiddleware);

Expand Down
Loading
Loading