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
15 changes: 11 additions & 4 deletions workspaces/x2a/plugins/x2a-backend/templates/x2a-job-script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -412,12 +412,19 @@ case "${PHASE}" in
echo "=== Step 2: Publishing to AAP ==="
echo "Command: uv run app.py publish-aap --target-repo ${TARGET_REPO_URL} --target-branch ${TARGET_REPO_BRANCH} --project-id ${PROJECT_DIR}"
cd /app
uv run app.py publish-aap \
PUBLISH_OUTPUT=$(uv run app.py publish-aap \
--target-repo "${TARGET_REPO_URL}" \
--target-branch "${TARGET_REPO_BRANCH}" \
--project-id "${PROJECT_DIR}"

ARTIFACTS+=("ansible_project:${PROJECT_DIR}/ansible-project")
--project-id "${PROJECT_DIR}" 2>&1 | tee /dev/stderr)

# Parse AAP project ID from output and construct URL
AAP_PROJECT_ID=$(echo "${PUBLISH_OUTPUT}" | grep -oP 'ID: \K[0-9]+' | tail -1)
if [ -n "${AAP_PROJECT_ID}" ]; then
ARTIFACTS+=("ansible_project:${AAP_CONTROLLER_URL}/execution/projects/${AAP_PROJECT_ID}/details")
else
echo "WARNING: Could not parse AAP project ID from publish-aap output"
ARTIFACTS+=("ansible_project:${AAP_CONTROLLER_URL}/execution/projects")
fi
;;

*)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,17 @@ export const ArtifactsCard = ({
<ItemField
label={t('modulePage.artifacts.ansible_project')}
value={
<ArtifactLink
artifact={ansibleProjectArtifact}
targetRepoUrl={targetRepoUrl}
targetRepoBranch={targetRepoBranch}
/>
ansibleProjectArtifact ? (
<Link
to={ansibleProjectArtifact.value}
target="_blank"
rel="noopener noreferrer"
>
{humanizeArtifactType(t, ansibleProjectArtifact.type)}
</Link>
) : (
t('module.phases.none')
)
}
/>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ export const ArtifactLink = ({
}) => {
const classes = styles();
const { t } = useTranslation();
const url =
artifact.type === 'ansible_project'
? artifact.value
: buildArtifactUrl(artifact.value, targetRepoUrl, targetRepoBranch);
return (
<Link
to={buildArtifactUrl(artifact.value, targetRepoUrl, targetRepoBranch)}
to={url}
target="_blank"
Comment on lines +42 to 49

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Unvalidated aap artifact url 🐞 Bug ⛨ Security

The UI now renders ansible_project using artifact.value directly as a link, while the backend
accepts aapCredentials.url as an arbitrary string and passes it into the job env. A malformed or
malicious URL (missing scheme, //host, or non-http(s) schemes) can create broken links or an
open-redirect-style phishing vector when users click the artifact.
Agent Prompt
### Issue description
`ansible_project` artifacts are now rendered as raw URLs (`artifact.value`) in the UI, but the backend currently accepts AAP `url` as an unconstrained string and propagates it into artifact generation. This can lead to malformed links and enables open-redirect-style phishing if a crafted URL is stored.

### Issue Context
- Backend accepts `aapCredentials.url` from requests and uses it to set `AAP_CONTROLLER_URL`.
- Job script builds the returned artifact URL from `AAP_CONTROLLER_URL`.
- Frontend renders the returned `ansible_project` artifact via `<Link to={artifact.value}>`.

### Fix Focus Areas
- workspaces/x2a/plugins/x2a-backend/src/router/modules.ts[210-232]
- workspaces/x2a/plugins/x2a-backend/src/services/JobResourceBuilder.ts[47-87]
- workspaces/x2a/plugins/x2a-backend/templates/x2a-job-script.sh[420-427]
- workspaces/x2a/plugins/x2a/src/components/ModuleTable/Artifacts.tsx[42-55]
- workspaces/x2a/plugins/x2a/src/components/ModulePage/ArtifactsCard.tsx[113-127]

### Implementation notes (non-exhaustive)
- Backend: change zod validation to require a real URL and restrict protocol to http/https (e.g., `z.string().url().refine(u => new URL(u).protocol in {"http:","https:"})`).
- Normalize by trimming trailing slash before persisting/using (or in the job script: `AAP_CONTROLLER_URL="${AAP_CONTROLLER_URL%/}"`).
- Frontend: for `ansible_project`, compute `href` like:
  - if `value` starts with `http://` or `https://` => use it
  - else => fallback to `buildArtifactUrl(value, targetRepoUrl, targetRepoBranch)` (covers older persisted artifacts / malformed values)
  - if invalid => render text (no link) or render a safe fallback page link.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

rel="noopener noreferrer"
key={artifact.id}
Expand Down
4 changes: 2 additions & 2 deletions workspaces/x2a/plugins/x2a/src/translations/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,13 @@ const x2aPluginTranslationDe = createTranslationMessages({
'module.statuses.error': 'Fehler',
'artifact.types.migrated_sources': 'Migrierte Quellen',
'artifact.types.project_metadata': 'Projektmetadaten',
'artifact.types.ansible_project': 'Ansible-Projekt',
'artifact.types.ansible_project': 'AAP-Projekt',
'modulePage.title': 'Moduldetails',
'modulePage.artifacts.title': 'Artefakte',
'modulePage.artifacts.migration_plan': 'Gesamter Projektmigrationsplan',
'modulePage.artifacts.module_migration_plan': 'Modulplan nach Analyse',
'modulePage.artifacts.migrated_sources': 'Migrierte Quellen',
'modulePage.artifacts.ansible_project': 'Ansible-Projekt',
'modulePage.artifacts.ansible_project': 'AAP-Projekt',
'modulePage.artifacts.description':
'Diese Artefakte werden durch den Konvertierungsprozess generiert und stehen zur Überprüfung bereit.',
'modulePage.phases.title': 'Migrationsphasen',
Expand Down
12 changes: 6 additions & 6 deletions workspaces/x2a/plugins/x2a/src/translations/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ const x2aPluginTranslationEs = createTranslationMessages({
'module.summary.toReview': 'para revisar',
'module.actions.runNextPhase': 'Ejecutar siguiente fase',
'module.currentPhase': 'Fase actual',
'module.lastUpdate': '��ltima actualización',
'module.lastUpdate': '��ltima actualización',
'module.notStarted': 'No iniciado',
'module.lastPhase': '��ltima fase',
'module.lastPhase': '��ltima fase',
'module.name': 'Nombre',
'module.status': 'Estado',
'module.sourcePath': 'Ruta de origen',
Expand All @@ -91,19 +91,19 @@ const x2aPluginTranslationEs = createTranslationMessages({
'module.statuses.none': '-',
'module.statuses.pending': 'Pendiente',
'module.statuses.running': 'En ejecución',
'module.statuses.success': '��xito',
'module.statuses.success': '��xito',
'module.statuses.error': 'Error',
'artifact.types.migrated_sources': 'Fuentes migradas',
'artifact.types.project_metadata': 'Metadatos del proyecto',
'artifact.types.ansible_project': 'Proyecto Ansible',
'artifact.types.ansible_project': 'Proyecto AAP',
'modulePage.title': 'Detalles del módulo',
'modulePage.artifacts.title': 'Artefactos',
'modulePage.artifacts.migration_plan':
'Plan de migración general del proyecto',
'modulePage.artifacts.module_migration_plan':
'Plan del módulo por análisis',
'modulePage.artifacts.migrated_sources': 'Fuentes migradas',
'modulePage.artifacts.ansible_project': 'Proyecto Ansible',
'modulePage.artifacts.ansible_project': 'Proyecto AAP',
'modulePage.artifacts.description':
'Estos artefactos son generados por el proceso de conversión y están disponibles para revisión.',
'modulePage.phases.title': 'Fases de migración',
Expand All @@ -116,7 +116,7 @@ const x2aPluginTranslationEs = createTranslationMessages({
'modulePage.phases.statuses.notStarted': 'No iniciado',
'modulePage.phases.statuses.pending': 'Pendiente',
'modulePage.phases.statuses.running': 'En ejecución',
'modulePage.phases.statuses.success': '��xito',
'modulePage.phases.statuses.success': '��xito',
'modulePage.phases.statuses.error': 'Error',
'modulePage.phases.reanalyzeInstructions':
'El plan de migración del módulo ya existe. Si el plan de migración general del proyecto se ha actualizado, vuelva a ejecutar el análisis para reflejar los cambios.',
Expand Down
4 changes: 2 additions & 2 deletions workspaces/x2a/plugins/x2a/src/translations/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,13 @@ const x2aPluginTranslationFr = createTranslationMessages({
'module.statuses.error': 'Erreur',
'artifact.types.migrated_sources': 'Sources migrées',
'artifact.types.project_metadata': 'Métadonnées du projet',
'artifact.types.ansible_project': 'Projet Ansible',
'artifact.types.ansible_project': 'Projet AAP',
'modulePage.title': 'Détails du module',
'modulePage.artifacts.title': 'Artefacts',
'modulePage.artifacts.migration_plan': 'Plan de migration global du projet',
'modulePage.artifacts.module_migration_plan': 'Plan du module par analyse',
'modulePage.artifacts.migrated_sources': 'Sources migrées',
'modulePage.artifacts.ansible_project': 'Projet Ansible',
'modulePage.artifacts.ansible_project': 'Projet AAP',
'modulePage.artifacts.description':
'Ces artefacts sont générés par le processus de conversion et sont disponibles pour examen.',
'modulePage.phases.title': 'Phases de migration',
Expand Down
4 changes: 2 additions & 2 deletions workspaces/x2a/plugins/x2a/src/translations/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,15 @@ const x2aPluginTranslationIt = createTranslationMessages({
'module.statuses.error': 'Errore',
'artifact.types.migrated_sources': 'Sorgenti migrate',
'artifact.types.project_metadata': 'Metadati del progetto',
'artifact.types.ansible_project': 'Progetto Ansible',
'artifact.types.ansible_project': 'Progetto AAP',
'modulePage.title': 'Dettagli del modulo',
'modulePage.artifacts.title': 'Artefatti',
'modulePage.artifacts.migration_plan':
'Piano di migrazione complessivo del progetto',
'modulePage.artifacts.module_migration_plan':
'Piano del modulo per analisi',
'modulePage.artifacts.migrated_sources': 'Sorgenti migrate',
'modulePage.artifacts.ansible_project': 'Progetto Ansible',
'modulePage.artifacts.ansible_project': 'Progetto AAP',
'modulePage.artifacts.description':
'Questi artefatti sono generati dal processo di conversione e sono disponibili per la revisione.',
'modulePage.phases.title': 'Fasi di migrazione',
Expand Down
4 changes: 2 additions & 2 deletions workspaces/x2a/plugins/x2a/src/translations/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const x2aPluginMessages = {
migration_plan: 'Overall project migration plan',
module_migration_plan: 'Module plan by analysis',
migrated_sources: 'Migrated Sources',
ansible_project: 'Ansible Project',
ansible_project: 'AAP Project',
description:
'These artifacts are generated by the conversion process and are available for review.',
},
Expand Down Expand Up @@ -175,7 +175,7 @@ export const x2aPluginMessages = {
module_migration_plan: 'Module Migration Plan',
migrated_sources: 'Migrated Sources',
project_metadata: 'Project Metadata',
ansible_project: 'Ansible Project',
ansible_project: 'AAP Project',
},
},
};
Expand Down