Skip to content

Commit e632aad

Browse files
Merge pull request #1284 from MervinPraison/fix/agent-init-type-hints
Fix/agent init type hints
2 parents b845520 + bbcc505 commit e632aad

16 files changed

Lines changed: 254 additions & 30 deletions

File tree

docker/Dockerfile.chat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
1616
# Install Python packages (using latest versions)
1717
RUN pip install --no-cache-dir \
1818
praisonai_tools \
19-
"praisonai>=4.5.113" \
19+
"praisonai>=4.5.114" \
2020
"praisonai[chat]" \
2121
"embedchain[github,youtube]"
2222

docker/Dockerfile.dev

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
2020
# Install Python packages (using latest versions)
2121
RUN pip install --no-cache-dir \
2222
praisonai_tools \
23-
"praisonai>=4.5.113" \
23+
"praisonai>=4.5.114" \
2424
"praisonai[ui]" \
2525
"praisonai[chat]" \
2626
"praisonai[realtime]" \

docker/Dockerfile.ui

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
1616
# Install Python packages (using latest versions)
1717
RUN pip install --no-cache-dir \
1818
praisonai_tools \
19-
"praisonai>=4.5.113" \
19+
"praisonai>=4.5.114" \
2020
"praisonai[ui]" \
2121
"praisonai[crewai]"
2222

src/praisonai-agents/praisonaiagents/context/ledger.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,20 @@ def __init__(self):
291291
self._shared_tokens: int = 0 # Tokens shared across agents
292292

293293
def get_agent_ledger(self, agent_id: str) -> ContextLedgerManager:
294-
"""Get or create ledger for an agent."""
294+
"""Get or create ledger for an agent.
295+
296+
Args:
297+
agent_id: Unique identifier for the agent.
298+
299+
Returns:
300+
ContextLedgerManager for this agent.
301+
302+
Raises:
303+
ValueError: If agent_id is empty or contains only whitespace.
304+
"""
305+
if not agent_id or not agent_id.strip():
306+
raise ValueError("agent_id must be a non-empty string")
307+
295308
if agent_id not in self._agents:
296309
self._agents[agent_id] = ContextLedgerManager(agent_id)
297310
return self._agents[agent_id]

src/praisonai-agents/praisonaiagents/context/monitor.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -514,18 +514,41 @@ def snapshot(
514514
return str(output_path)
515515

516516
def _get_output_path(self, agent_name: str = "") -> Path:
517-
"""Get output path, optionally per-agent."""
517+
"""Get output path, optionally per-agent.
518+
519+
Security: Agent names are sanitized to prevent path traversal.
520+
"""
518521
if not self.multi_agent_files or not agent_name:
519522
return self.path
520523

521524
# Create per-agent path
522525
stem = self.path.stem
523526
suffix = self.path.suffix or (".json" if self.format == "json" else ".txt")
524527

525-
# Sanitize agent name
526-
safe_name = re.sub(r'[^\w\-]', '_', agent_name.lower())
527-
528-
return self.path.parent / f"{stem}_{safe_name}{suffix}"
528+
# Sanitize agent name:
529+
# 1. Strip any path separators first (defence in depth)
530+
safe_name = agent_name.replace('/', '_').replace('\\', '_')
531+
# 2. Collapse to safe chars only
532+
safe_name = re.sub(r'[^\w\-]', '_', safe_name.lower())
533+
# 3. Enforce length limit to prevent filesystem issues
534+
safe_name = safe_name[:64] if safe_name else 'unknown'
535+
# 4. Strip leading/trailing underscores / dots
536+
safe_name = safe_name.strip('_.')
537+
if not safe_name:
538+
safe_name = 'unknown'
539+
540+
result = self.path.parent / f"{stem}_{safe_name}{suffix}"
541+
542+
# Verify the resolved path stays within the expected parent dir
543+
resolved = result.resolve()
544+
parent_resolved = self.path.parent.resolve()
545+
if not str(resolved).startswith(str(parent_resolved) + '/') and resolved.parent != parent_resolved:
546+
# Fallback to a safe hash-based name
547+
import hashlib
548+
hash_name = hashlib.sha256(agent_name.encode()).hexdigest()[:16]
549+
result = self.path.parent / f"{stem}_{hash_name}{suffix}"
550+
551+
return result
529552

530553
def _get_warnings(
531554
self,

src/praisonai-agents/praisonaiagents/tools/python_tools.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,15 @@ def safe_execute():
143143
blocked_attrs = {{
144144
'__subclasses__', '__bases__', '__mro__', '__globals__',
145145
'__code__', '__class__', '__dict__', '__builtins__',
146-
'__import__', '__loader__', '__spec__'
146+
'__import__', '__loader__', '__spec__', '__init_subclass__',
147+
'__set_name__', '__reduce__', '__reduce_ex__',
148+
'__traceback__', '__qualname__', '__module__',
149+
'__wrapped__', '__closure__', '__annotations__',
150+
# Frame/code object introspection
151+
'gi_frame', 'gi_code', 'cr_frame', 'cr_code',
152+
'ag_frame', 'ag_code', 'tb_frame', 'tb_next',
153+
'f_globals', 'f_locals', 'f_builtins', 'f_code',
154+
'co_consts', 'co_names',
147155
}}
148156
149157
for node in ast.walk(tree):
@@ -162,7 +170,9 @@ def safe_execute():
162170
"success": False
163171
}}
164172
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
165-
if node.func.id in ('exec', 'eval', 'compile', '__import__', 'open'):
173+
if node.func.id in ('exec', 'eval', 'compile', '__import__',
174+
'open', 'input', 'breakpoint',
175+
'setattr', 'delattr', 'dir'):
166176
return {{
167177
"result": None,
168178
"stdout": "",

src/praisonai-agents/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "praisonaiagents"
7-
version = "1.5.113"
7+
version = "1.5.114"
88
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/praisonai-agents/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/praisonai-ts/src/workflows/yaml-parser.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import { AgentFlow, Task, TaskConfig } from './index';
7+
import * as path from 'path';
78

89
export interface YAMLWorkflowDefinition {
910
name: string;
@@ -39,8 +40,9 @@ export interface ParsedWorkflow {
3940
* Parse YAML string into workflow definition
4041
*/
4142
export function parseYAMLWorkflow(yamlContent: string): YAMLWorkflowDefinition {
42-
// Simple YAML parser for workflow definitions
43-
// For production, use js-yaml package
43+
// SECURITY: If migrating to js-yaml, you MUST use:
44+
// yaml.load(content, { schema: yaml.JSON_SCHEMA })
45+
// Never use yaml.load() with DEFAULT_SCHEMA — it enables arbitrary JS execution.
4446
const lines = yamlContent.split('\n');
4547
const result: YAMLWorkflowDefinition = {
4648
name: '',
@@ -82,7 +84,15 @@ export function parseYAMLWorkflow(yamlContent: string): YAMLWorkflowDefinition {
8284
type: 'agent'
8385
};
8486
} else if (currentStep) {
85-
// Step properties
87+
// Step properties — whitelist allowed keys to prevent injection
88+
const ALLOWED_STEP_KEYS = new Set([
89+
'type', 'agent', 'tool', 'input', 'output', 'condition',
90+
'onError', 'maxRetries', 'timeout', 'loopCondition', 'maxIterations',
91+
]);
92+
if (!ALLOWED_STEP_KEYS.has(key)) {
93+
// Ignore unknown keys — do not allow arbitrary property injection
94+
continue;
95+
}
8696
if (key === 'type') currentStep.type = value as any;
8797
else if (key === 'agent') currentStep.agent = value;
8898
else if (key === 'tool') currentStep.tool = value;
@@ -265,9 +275,33 @@ function parseValue(value: string): any {
265275
export async function loadWorkflowFromFile(
266276
filePath: string,
267277
agents: Record<string, any> = {},
268-
tools: Record<string, any> = {}
278+
tools: Record<string, any> = {},
279+
options: { basePath?: string; maxFileSizeBytes?: number } = {}
269280
): Promise<ParsedWorkflow> {
270281
const fs = await import('fs/promises');
282+
283+
// SECURITY: Prevent path traversal
284+
const normalizedPath = path.normalize(filePath);
285+
if (normalizedPath.includes('..')) {
286+
throw new Error('Path traversal detected: ".." is not allowed in file paths');
287+
}
288+
289+
// If basePath is specified, ensure resolvedPath stays within it
290+
if (options.basePath) {
291+
const resolvedBase = path.resolve(options.basePath);
292+
const resolvedFile = path.resolve(options.basePath, normalizedPath);
293+
if (!resolvedFile.startsWith(resolvedBase + path.sep) && resolvedFile !== resolvedBase) {
294+
throw new Error(`File path must be within base directory: ${options.basePath}`);
295+
}
296+
}
297+
298+
// SECURITY: Enforce file size limit (default 1 MB)
299+
const maxSize = options.maxFileSizeBytes ?? 1_048_576;
300+
const stat = await fs.stat(filePath);
301+
if (stat.size > maxSize) {
302+
throw new Error(`File too large: ${stat.size} bytes exceeds limit of ${maxSize} bytes`);
303+
}
304+
271305
const content = await fs.readFile(filePath, 'utf-8');
272306
const definition = parseYAMLWorkflow(content);
273307
return createWorkflowFromYAML(definition, agents, tools);

src/praisonai/praisonai.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ class Praisonai < Formula
33

44
desc "AI tools for various AI applications"
55
homepage "https://github.com/MervinPraison/PraisonAI"
6-
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.5.113.tar.gz"
7-
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.5.113.tar.gz | shasum -a 256`.split.first
6+
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.5.114.tar.gz"
7+
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.5.114.tar.gz | shasum -a 256`.split.first
88
license "MIT"
99

1010
depends_on "python@3.11"

0 commit comments

Comments
 (0)