feat: Add foundation for Jira issue creation workflow#166
feat: Add foundation for Jira issue creation workflow#166
Conversation
Wiz Scan Summary
To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension. |
|
|
||
| # Render template | ||
| try: | ||
| template = Template(template_content) |
There was a problem hiding this comment.
Jinja2 template rendering without autoescaping (CWE-116)
More Details
The application was found using the Jinja2 templating engine without enabling autoescaping. This could lead to Cross-Site Scripting (XSS) vulnerabilities when rendering user-supplied input in an HTML context. XSS attacks allow an attacker to inject malicious scripts into web pages, potentially compromising sensitive data or hijacking user sessions.
| Attribute | Value |
|---|---|
| Impact | |
| Likelihood |
Remediation
Jinja2 is a popular templating engine for Python that allows developers to generate dynamic HTML pages. By default, Jinja2 does not automatically escape user-supplied data, which can lead to Cross-Site Scripting (XSS) vulnerabilities if the data is rendered in the HTML output without proper sanitization. XSS vulnerabilities allow attackers to inject malicious scripts into web pages, potentially compromising the application and its users.
To mitigate this vulnerability, Jinja2 provides an autoescape option that automatically escapes all variables rendered in the template. When autoescape is set to True, Jinja2 will escape all variables by default, preventing XSS attacks. It is recommended to enable autoescape globally for the entire application or selectively for specific templates.
Code examples:
// VULNERABLE CODE - Jinja2 environment without autoescape enabled
import jinja2
env = jinja2.Environment()
template = env.from_string("Hello {{ name }}")
print(template.render(name="<script>alert('XSS')</script>"))
// SECURE CODE - Jinja2 environment with autoescape enabled
import jinja2
env = jinja2.Environment(autoescape=True)
template = env.from_string("Hello {{ name }}")
print(template.render(name="<script>alert('XSS')</script>"))
Additional recommendations:
- Follow the OWASP XSS Prevention Cheat Sheet for best practices on handling user input and preventing XSS vulnerabilities.
- Regularly update your dependencies, including Jinja2, to ensure you have the latest security patches and features.
- Consider using a Content Security Policy (CSP) to further mitigate XSS risks by restricting the execution of inline scripts and other potentially dangerous content.
- Implement input validation and sanitization mechanisms to ensure user input is properly validated and sanitized before rendering.
- Conduct regular security testing, including penetration testing and code reviews, to identify and address potential vulnerabilities.
Rule ID: WS-I007-PYTHON-00008
To ignore this finding as an exception, reply to this conversation with #wiz_ignore reason
If you'd like to ignore this finding in all future scans, add an exception in the .wiz file (learn more) or create an Ignore Rule (learn more).
To get more details on how to remediate this issue using AI, reply to this conversation with #wiz remediate
|
|
||
| [plugins.jira] | ||
| enabled = false | ||
| enabled = true |
There was a problem hiding this comment.
This project does not use Jira so this should be false
| find_ready_to_dev_transition, | ||
| transition_issue_to_ready_for_dev, | ||
| ) | ||
| from titan_plugin_jira.models import UITransition |
There was a problem hiding this comment.
This does not exist, the model is called UiJiraTransition. Also you are instanciating UITransition(has_screen=False) but this does not exist in UiJiraTransition
|
|
||
| return self._metadata_service.list_project_versions(key) | ||
|
|
||
| def get_priorities(self) -> ClientResult[List["UIPriority"]]: |
There was a problem hiding this comment.
This comment belong to line 333 not to this, but I couldn't add it there. Now you are here, if you can change this: ClientResult[List[NetworkJiraIssueType]] UI Model should be returned.
There was a problem hiding this comment.
Also this ones:
def list_statuses(...) -> ClientResult[List[dict]]:
def get_current_user(...) -> ClientResult[dict]:
def list_project_versions(...)-> ClientResult[List[dict]]:
This should return UIModel
There was a problem hiding this comment.
Also in this function 'get_priorities' makes a map, and this should be done in the service. Never map in the client
There was a problem hiding this comment.
In the function create_issue, there are Busssines logic here that () should be in the service IssueService or in the operator.
I mean this:
if isinstance(issue_types_result, ClientError): # ❌ también: isinstance en vez de match
return issue_types_result
issue_type_obj = None
for it in issue_types_result.data: # ← lógica de búsqueda en el Client
if it.name.lower() == issue_type.lower():
|
|
||
| def find_ready_to_dev_transition( | ||
| jira_client: "JiraClient", issue_key: str | ||
| ) -> ClientResult["UITransition"]: |
There was a problem hiding this comment.
This should not return the ClientResult, just the UITransition
|
|
||
| def transition_issue_to_ready_for_dev( | ||
| jira_client: "JiraClient", issue_key: str | ||
| ) -> ClientResult[None]: |
There was a problem hiding this comment.
ClientResult, should not be returned in operations. It's just to Client/Service. The step that calls this should call try/except
| - index: Zero-based index if valid, None otherwise | ||
| - error_message: Error description if invalid, None otherwise | ||
|
|
||
| Examples: |
There was a problem hiding this comment.
Remove the examples from the docstrings
|
|
||
| # Get priorities from Jira | ||
| priorities = None | ||
| try: |
There was a problem hiding this comment.
Should not use try/except here, just send ClientError or ClientResult
|
|
||
| selected_type = issue_types[index] | ||
|
|
||
| if not selected_type: |
There was a problem hiding this comment.
It should never be false here
| match transition_result: | ||
| case ClientSuccess(): | ||
| # Get transition details to show user | ||
| find_result = ctx.jira.get_transitions(issue_key) |
There was a problem hiding this comment.
You are calling the api twice. In line 128 you have already called the api. You need to add the necessary data to the clientSuccess.message, or in the data model
| value = int(selection) | ||
| index = value - 1 | ||
|
|
||
| if index < 0 or index >= max_value: |
There was a problem hiding this comment.
You should validate min_value as well
| """ | ||
| ctx.textual.begin_step(StepTitles.DESCRIPTION) | ||
|
|
||
| ctx.textual.markdown("## 📝 Task Description") |
There was a problem hiding this comment.
This should not be a Markdown. Maybe better a bold_text
| """ | ||
| ctx.textual.begin_step(StepTitles.ISSUE_TYPE) | ||
|
|
||
| ctx.textual.markdown("## 🏷️ Issue Type") |
There was a problem hiding this comment.
This should not be a Markdown, maybe better a bold_text
| """ | ||
| ctx.textual.begin_step(StepTitles.PRIORITY) | ||
|
|
||
| ctx.textual.markdown("## 🔥 Priority") |
There was a problem hiding this comment.
These should not be a markdown. Check the rest of the steps, cause the title of something's should not be a markdown,
finxo
left a comment
There was a problem hiding this comment.
Also testing the PR I found this bug:
────────────────────────────────────────────────────────────────────────────────
SESSION START 2026-03-02 10:14:41 UTC PID 85720
────────────────────────────────────────────────────────────────────────────────
{"version": "0.1.11", "mode": "development", "log_level": "WARNING", "pid": 85720, "log_file": "/home/alex/.local/state/titan/logs/titan.log", "event": "session_started", "level": "info", "logger": "titan", "timestamp": "2026-03-02T10:14:41.119017Z"}
{"name": "git", "event": "plugin_initialized", "level": "info", "logger": "titan_cli.core.plugins.plugin_registry", "timestamp": "2026-03-02T10:14:41.150177Z"}
{"name": "jira", "event": "plugin_initialized", "level": "info", "logger": "titan_cli.core.plugins.plugin_registry", "timestamp": "2026-03-02T10:14:41.226858Z"}
{"name": "github", "event": "plugin_initialized", "level": "info", "logger": "titan_cli.core.plugins.plugin_registry", "timestamp": "2026-03-02T10:14:41.591243Z"}
{"message": "Status retrieved", "result_type": "UIGitStatus", "duration": 0.015, "event": "get_status_success", "level": "info", "logger": "titan_plugin_git.clients.services.status_service", "timestamp": "2026-03-02T10:14:41.623199Z"}
{"message": "Status retrieved", "result_type": "UIGitStatus", "duration": 0.022, "event": "get_status_success", "level": "info", "logger": "titan_plugin_git.clients.services.status_service", "timestamp": "2026-03-02T10:14:42.668828Z"}
{"message": "Status retrieved", "result_type": "UIGitStatus", "duration": 0.021, "event": "get_status_success", "level": "info", "logger": "titan_plugin_git.clients.services.status_service", "timestamp": "2026-03-02T10:14:45.829840Z"}
{"message": "Status retrieved", "result_type": "UIGitStatus", "duration": 0.021, "event": "get_status_success", "level": "info", "logger": "titan_plugin_git.clients.services.status_service", "timestamp": "2026-03-02T10:15:00.475315Z"}
{"workflow": "Create Jira Issue", "source": "plugin", "total_steps": 7, "is_nested": false, "event": "workflow_started", "level": "info", "logger": "titan_cli.ui.tui.textual_workflow_executor", "timestamp": "2026-03-02T10:15:00.581044Z"}
{"workflow": "Create Jira Issue", "step_id": "description", "message": "Brief description captured: 87 characters", "duration": 16.898, "event": "step_success", "level": "info", "logger": "titan_cli.ui.tui.textual_workflow_executor", "timestamp": "2026-03-02T10:15:17.485003Z"}
{"message": "Found 21 issue types", "result_type": "list", "duration": 0.164, "event": "get_issue_types_success", "level": "info", "logger": "titan_plugin_jira.clients.services.metadata_service", "timestamp": "2026-03-02T10:15:17.657802Z"}
{"workflow": "Create Jira Issue", "step_id": "issue_type", "message": "Issue type selected: Epic", "duration": 13.333, "event": "step_success", "level": "info", "logger": "titan_cli.ui.tui.textual_workflow_executor", "timestamp": "2026-03-02T10:15:30.846453Z"}
{"workflow": "Create Jira Issue", "step_id": "priority", "message": "Priority selected: Highest", "duration": 10.62, "event": "step_success", "level": "info", "logger": "titan_cli.ui.tui.textual_workflow_executor", "timestamp": "2026-03-02T10:15:41.478548Z"}
{"event": "HTTP Request: POST https://llm.tools.cloud.masorange.es/v1/messages \"HTTP/1.1 200 OK\"", "timestamp": "2026-03-02T10:15:58.747430Z"}
{"workflow": "Create Jira Issue", "step_id": "generate_with_ai", "error": "Error executing step 'ai_enhance_issue_description' from plugin 'jira': object of type 'NoneType' has no len()", "on_error": "fail", "duration": 17.276, "event": "step_failed", "level": "error", "logger": "titan_cli.ui.tui.textual_workflow_executor", "timestamp": "2026-03-02T10:15:58.755240Z"}
{"workflow": "Create Jira Issue", "failed_at_step": "generate_with_ai", "error": "Error executing step 'ai_enhance_issue_description' from plugin 'jira': object of type 'NoneType' has no len()", "steps_completed": 4, "duration": 58.186, "event": "workflow_failed", "level": "error", "logger": "titan_cli.ui.tui.textual_workflow_executor", "timestamp": "2026-03-02T10:15:58.761589Z"}
● Encontré el bug. La cadena del problema:
1. _parse_ai_response inicializa "title": ""
2. En el cleanup final: si queda vacío, lo pone a None → sections["title"] = None
3. parsed.pop("title", DEFAULT_TITLE) devuelve None (la clave existe pero vale None — el default solo aplica si la clave no existe)
4. len(None) → TypeError
Pull Request
📝 Summary
This PR lays the groundwork for integrating a Jira issue creation workflow. It enables the Jira plugin in the configuration and refactors the
create_branch_stepto use a more robustResultobject pattern for handling git operations. This shift from exception-based error handling to amatch/casestructure improves code clarity and resilience.🔧 Changes Made
[plugins.jira]in.titan/config.toml.create_branch_step.pyto useClientSuccessandClientErrorresult objects instead of raisingGitErrorexceptions.try/exceptblocks withmatchstatements for handling the outcomes of all git commands (get_branches,checkout,delete_branch,create_branch).resulttypes fromtitan_cli.core.result.🧪 Testing
poetry run pytest)make test)titan-devThe refactoring touches core git functionality. Existing unit tests for
create_branch_stepshould be updated to validate the newResult-based logic, ensuring all success and error paths are handled correctly.📊 Logs
✅ Checklist