Skip to content

SECURITY: Escape filetree_create exports, keep its output inside output_path, and mark inventory plugin data unsafe - #340

Merged
cigamit merged 5 commits into
mainfrom
security-safe-exports
Oct 27, 2026
Merged

cigamit merged 5 commits into
mainfrom
security-safe-exports

Conversation

@cigamit

@cigamit cigamit commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

Reported by Quanah Gibson-Mount @quanah

Summary

This fixes three issues where text stored in the controller can be templated or written somewhere unintended on the machine that runs Ansible. Two were reported externally against filetree_create. The third, in the controller inventory plugin, was found while validating those reports.

Changes

filetree_create: export escaping

The export templates wrote controller strings into YAML without escaping. When filetree_read loaded the files, Jinja in a name or description was evaluated on the control node. A label named ops{{ 6 * 7 }} came back as ops42. A quote and a newline in a value could also add new keys to the exported entry.

  • New filter ctrliq.ascender.export_yaml writes each value as one line of flow-style YAML. Strings are double-quoted and tagged !unsafe.
  • All 22 templates in roles/filetree_create/templates/ and playbooks/flatten_filetree_create_output.yaml now print values only through this filter.
  • tests/unit/test_filetree_create_name_injection.py renders every template, loads it back with include_vars and checks the values. A gate parses each template and fails if any printed expression skips the filter.

filetree_create: output paths

Output folders and file names were built from controller names with only / replaced. An organization named .. wrote its export into the parent of output_path. The credentials folder task used the organization name with no replacement, so ../../x created folders outside output_path.

  • New filter ctrliq.ascender.path_component replaces / and NUL, rewrites ., .. and empty names, and shortens names over 230 bytes with a hash suffix.
  • Every path in roles/filetree_create/tasks/ built from controller data goes through it, including the credentials folder.
  • tests/unit/test_filetree_create_paths.py runs the role against a fake API and checks where every file lands.

Inventory plugin: unsafe marking

plugins/inventory/controller.py passed host and group variables from the controller to Ansible as plain strings. Jinja stored in a host or group variable was evaluated on the node running Ansible. Host and group names, variables and controller_metadata are now wrapped with wrap_var.

  • tests/unit/test_inventory_plugin_unsafe.py feeds the plugin a stubbed API response and checks that the values stay literal. Three of its four tests fail without the fix.

Behavior changes

  • Exports write booleans and numbers as native YAML instead of quoted strings. Values such as yes or 1.10 keep their string type.
  • Readers that load exports with plain PyYAML need a constructor for the !unsafe tag. filetree_read handles it already.
  • Names over 230 bytes, and objects named . or .., get new file names. filetree_read reads file contents, not names, so loading is unaffected.
  • Templates stored in host or group variables are no longer evaluated when the inventory is used through this plugin. Ascender's own jobs are unaffected.

Testing

pytest --noconftest tests/unit/test_filetree_create_name_injection.py tests/unit/test_filetree_create_paths.py tests/unit/test_inventory_plugin_unsafe.py

  • 99 passed locally on ansible-core 2.15.13. That is below the collection's 2.16 minimum, so please rely on CI for 2.16 and later.
  • ruff check is clean on the changed plugin and the new test.
  • The Django-based unit suite was not run locally.

Changelog fragments: filetree-create-export-escaping.yml, filetree-create-path-components.yml, inventory-plugin-unsafe.yml.

…ut_path, and mark inventory plugin data unsafe

Reported by Quanah Gibson-Mount @quanah
@cigamit
cigamit requested a review from TheWitness October 27, 2026 20:48
@cigamit cigamit self-assigned this Oct 27, 2026
Copilot AI balanced review requested due to automatic review settings October 27, 2026 20:48
@ciq-it-service-account

ciq-it-service-account commented Oct 27, 2026 •

Copy link
Copy Markdown

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues
✅ Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

All three literal-value tests fail on ansible-core 2.19+ because the untrusted outer template expression remains unevaluated, even when the plugin is correct.

1 open issue
What changed in this PR

This PR hardens the collection’s controller export and inventory-loading paths against unintended Jinja evaluation, YAML injection, and directory traversal.

Changes:

  • Adds safe YAML serialization across export templates and the flattening playbook.
  • Sanitizes controller-derived path components, including special names and oversized filenames.
  • Marks inventory data unsafe and adds regression tests and changelog entries.
File Description
tests/​unit/​test_inventory_plugin_unsafe.py Tests literal inventory values and preserved structure.
tests/​unit/​test_filetree_create_paths.py Tests path containment and filename sanitization.
tests/​unit/​test_filetree_create_name_injection.py Tests export round trips and escaping enforcement.
roles/​filetree_create/​templates/​current_workflow_job_templates.j2 Escapes workflow, node, and survey exports.
roles/​filetree_create/​templates/​current_users.j2 Escapes user account exports.
roles/​filetree_create/​templates/​current_user_roles.j2 Escapes user role assignments.
roles/​filetree_create/​templates/​current_teams.j2 Escapes team exports.
roles/​filetree_create/​templates/​current_team_roles.j2 Escapes team role assignments.
roles/​filetree_create/​templates/​current_settings.j2 Safely serializes settings and native types.
roles/​filetree_create/​templates/​current_schedules.j2 Escapes schedule fields and nested data.
roles/​filetree_create/​templates/​current_projects.j2 Escapes project exports.
roles/​filetree_create/​templates/​current_organizations.j2 Escapes organizations and related names.
roles/​filetree_create/​templates/​current_notification_templates.j2 Escapes notification configuration and messages.
roles/​filetree_create/​templates/​current_labels.j2 Escapes label exports.
roles/​filetree_create/​templates/​current_job_templates.j2 Escapes job templates, variables, and surveys.
roles/​filetree_create/​templates/​current_inventory_sources.j2 Escapes inventory source exports.
roles/​filetree_create/​templates/​current_inventories.j2 Escapes inventory fields and variables.
roles/​filetree_create/​templates/​current_instances.j2 Safely serializes instance fields and peers.
roles/​filetree_create/​templates/​current_instance_groups.j2 Escapes instance group exports.
roles/​filetree_create/​templates/​current_hosts.j2 Escapes host fields and variables.
roles/​filetree_create/​templates/​current_groups.j2 Escapes group variables and membership lists.
roles/​filetree_create/​templates/​current_execution_environments.j2 Escapes execution environment exports.
roles/​filetree_create/​templates/​current_credentials.j2 Escapes credential inputs and blanks encrypted markers.
roles/​filetree_create/​templates/​current_credential_types.j2 Escapes credential schemas and injectors.
roles/​filetree_create/​templates/​current_applications.j2 Escapes application exports.
roles/​filetree_create/​tasks/​workflow_job_templates.yml Sanitizes workflow export paths.
roles/​filetree_create/​tasks/​users.yml Sanitizes user export paths.
roles/​filetree_create/​tasks/​user_roles.yml Sanitizes user role filenames.
roles/​filetree_create/​tasks/​teams.yml Sanitizes team export paths.
roles/​filetree_create/​tasks/​team_roles.yml Sanitizes team role filenames.
roles/​filetree_create/​tasks/​schedules.yml Sanitizes schedule filenames.
roles/​filetree_create/​tasks/​projects.yml Sanitizes project export paths.
roles/​filetree_create/​tasks/​organizations.yml Sanitizes organization directories.
roles/​filetree_create/​tasks/​notification_templates.yml Sanitizes notification export paths.
roles/​filetree_create/​tasks/​labels.yml Sanitizes label export paths.
roles/​filetree_create/​tasks/​job_templates.yml Sanitizes job template export paths.
roles/​filetree_create/​tasks/​inventory.yml Sanitizes inventory and related output paths.
roles/​filetree_create/​tasks/​credentials.yml Consistently sanitizes credential directories and filenames.
roles/​filetree_create/​tasks/​constructed_inventory.yml Sanitizes constructed inventory paths.
roles/​filetree_create/​tasks/​applications.yml Sanitizes application export paths.
plugins/​inventory/​controller.py Wraps controller inventory data and metadata as unsafe.
plugins/​filter/​path_component.py Adds path sanitization and byte-limited shortening.
plugins/​filter/​export_yaml.py Adds escaped, type-aware YAML serialization.
playbooks/​flatten_filetree_create_output.yaml Uses safe serialization for flattened exports.
changelogs/​fragments/​inventory-plugin-unsafe.yml Documents inventory safety and compatibility changes.
changelogs/​fragments/​filetree-create-path-components.yml Documents path containment and filename fixes.
changelogs/​fragments/​filetree-create-export-escaping.yml Documents export security and serialization changes.

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 27, 2026 21:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

YAML alias copying can cause exponential memory exhaustion, and the CI unit job installs a package unavailable on its runner, preventing tests from running.

2 open issues
1 previously missed issue

In code that hasn't changed since last review

Low severity Test unsafe variable marking on older ansible-core versions

plugins/​inventory/​controller.py:179

The CI version matrix runs only sanity checks, while the pytest job installs the latest ansible-core. On 2.19+, controller data is already untrusted, so these inventory tests pass even if the wrap_var calls are removed. Add a standalone CI run of the unsafe-marking tests on supported ansible-core 2.16–2.18 with a compatible Python version.

1 resolved since last review

Review effort: Balanced

Comment thread .github/workflows/ci.yml Resolved
Comment thread plugins/filter/export_yaml.py Resolved
Copilot AI balanced review requested due to automatic review settings October 27, 2026 21:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved critical issues can cause CI installation failures, exhaust memory through YAML alias expansion, and let credential paths escape the export root through environment-variable expansion.

1 open issue
1 previously missed issue

In code that hasn't changed since last review

Medium severity Run unsafe-marking regression tests across supported Ansible versions

tests/​unit/​test_inventory_plugin_unsafe.py:81

On ansible-core 2.19+, these assertions also pass if the inventory plugin's wrap_var calls are removed, because controller values are already untrusted. CI installs the latest core for unit tests (.github/workflows/ci.yml:198); its supported-version matrix runs only sanity checks. Please run this standalone regression suite on supported affected versions such as 2.16 and 2.18 in CI, so the unsafe-marking fix is actually protected against regressions.

2 resolved since last review

Review effort: Balanced

Comment thread plugins/filter/path_component.py Resolved
Copilot AI balanced review requested due to automatic review settings October 27, 2026 22:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

YAML container copying can cause exponential memory growth from shared aliases, and the CI dependency installation can fail on Ubuntu 24.04 runners before tests execute.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@cigamit
cigamit requested a review from pswartzclan October 27, 2026 23:19
@cigamit
cigamit merged commit 84b2fc9 into main Oct 27, 2026
16 of 17 checks passed
@cigamit
cigamit deleted the security-safe-exports branch October 27, 2026 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

5 participants