Repository navigation
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
… tool commands Add planemo/linters/security.py, an opt-in module of eight informational checks that flag common patterns where user-controlled values reach a tool <command> template in a way that can carry shell metacharacters: - SecurityRawIdentifier: raw collection element_identifier interpolated into the command - SecurityRawCollectionName: collection .name / .keys() used raw - SecurityGroupTag / SecurityDataColumn: unsanitized group_tag and data_column values - SecuritySanitizerDisabled / SecurityToolSanitizeOff: sanitization disabled per parameter or tool-wide (<options sanitize="false"/>) - SecurityPermissiveSanitizer: per-parameter sanitizer declarations that let shell metacharacters survive (incl. dangerous invalid_char and mapping targets) - SecurityUnquotedParam: text params interpolated unquoted, unless a regex validator restricts the value to safe characters The checks recognize common mitigations - re.sub/shlex.quote cleaning of values, quoted interpolation, Cheetah #if/#elif/#while condition lines, bash comments - to limit false positives. Cheetah comments (## and #* ... *#) and directives are skipped when tracking shell quoting, since Cheetah removes them before the shell sees the command. Macro expansion performed by tool loading is fully visible to the checks, including #for loop variable binding. All findings are INFO level, so plain 'planemo lint' never fails; the module runs only with the new --security flag and can be skipped via '--skip security' or per class. Also adds fixtures and tests, and regenerates the affected docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rplanel
force-pushed
the
security-linter
branch
from
November 10, 2026 21:29
933be89 to
2ffe63c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Galaxy tool wrappers commonly interpolate user-controlled values — collection element identifiers,
group_taganddata_columnvalues, free-text parameters — directly into<command>templates. Where parameter sanitization is disabled (<sanitizer sanitize="false"/>,<options sanitize="false"/>) or reconfigured to let shell metacharacters through, such interpolations can allow command injection. Finding these patterns today is a manual, expert-driven review process; nothing in the tool-development toolchain points maintainers at the highest-risk idioms.What changed
This PR adds an opt-in, informational security linter module,
planemo/linters/security.py, with oneLinterclass per check, following planemo's existing linter conventions:SecurityRawIdentifierelement_identifierof a dataset/collection interpolated into the commandSecurityRawCollectionName.name/.keys()used raw in the commandSecurityGroupTaggroup_tagparam value used in the command (never sanitized by Galaxy)SecurityDataColumndata_columnparam value used in the command (never sanitized)SecuritySanitizerDisabled<sanitizer sanitize="false"/>SecurityToolSanitizeOff<options sanitize="false"/>SecurityPermissiveSanitizerinvalid_char)SecurityUnquotedParamtextparams interpolated unquoted, unless a regex validator restricts the value to a safe character setKey properties:
planemo lint; the module runs only when the new--securityflag is passed (available on bothplanemo lintandplanemo shed lintthrough the shared linter option group).--skip security(module name added to the known-skip list inplanemo/lint.py), and each class individually, e.g.--skip SecurityRawIdentifier.ToolParameterSanitizer. The permissive-sanitizer check models Galaxy's preset resolution (string.*constants,default/nonepresets,<valid>add/remove,<mapping>add/remove,invalid_char), so findings reflect what actually survives sanitization.re.subwith a safe whitelisted character class (e.g.re.sub(r'[^\w.-]', '_', ...)) orshlex.quote; quoted interpolation; Cheetah condition directives (#if/#elif/#while/#unless) whose contents are evaluated but not rendered; bash comments (the rest of their line is inert, later lines still run); and text params constrained by an anchored safe-character-set regex validator.<expand>/<token>content is fully visible to the checks, including Cheetah#forloop-variable binding (loop variables are tracked to the collections they iterate).argument="..."(without an explicitname) are indexed the same way Galaxy derives parameter names.safe_element_identifieraccessor (PR Implement Tool Pattern for Using Filename Safe Collection Identifiers galaxy#23735) and, for older servers, there.subcleaning fallback.Docs:
--securityappears in the regenerateddocs/commands/lint.rstanddocs/commands/shed_lint.rst;docs/planemo.linters.rstlists the new module.Evidence
planemo lint --securityon the new fixture (other lint messages trimmed; note the exit code — INFO findings do not fail the run):Suppression paths, on the same fixture:
A second fixture (
security_clean_tool.xml) uses cleaned identifiers, quoted interpolation, and restricted sanitizers and produces zero security findings; a third (security_unquoted_param.xml) contains two unquoted text params of which only the one without a safe regex validator is reported.Tests: unit tests in
tests/test_planemo_linters.py(exact message assertions per fixture, clean-tool silence, module-name skip, loop-scope and comment semantics) and a CLI testtest_lint.py::LintTestCase::test_lint_securitycovering the flag and the module skip.flake8,black,isort,ruffandmypypass on the changed files; the two pre-existing toil-engine test failures onmasterare unrelated (they fail identically without this change).The change was reviewed in three rounds by an AI reviewer (Claude Opus 5.5); all 41 findings were fixed and pinned by tests. That review log is collapsed in the comments below.
Details / limitations
<configfile>contents (shell/R/Python injection inside generated scripts),eval()-style sinks and tool launchers, and values derived from file content (e.g. BED column parsing).ELI5
Some Galaxy tools paste user-supplied names and text straight into shell commands. If the usual "cleaning" of those values is switched off or too permissive, a crafted name can run commands on the machine running the tool. This PR adds an optional
planemo lint --securitymode that points out these patterns — as hints for maintainers to review, never as failures — and stays quiet when the wrapper already quotes or cleans the value.Known, not fixed
Problems I know of and have deliberately left in this PR, for the reviewer to rule on:
<configfile>contents (shell/R/Python injection inside generated scripts),eval()-style sinks and tool launchers, helper/generated scripts, and values derived from file content (e.g. BED column parsing) are out of scope for this first version.SecuritySampleSheetColumns; it is implemented asSecurityDataColumn(consistent withdata_columnparam type naming).dangerous_invalid_charfinding and recognition ofshlex.quote(...)as a safe cleaning idiom.Lintersubclasses repeat an identical dispatch shape (a loop + string filter each) — kept to mirror the established linter-module patterns (urls.py,doi.py).docs/planemo.linters.rstgains an autodoc entry for the pre-existingplanemo.linters.utilmodule — a regeneration side effect, not part of this feature.