Skip to content

fix: block unvalidated subcommand arguments in execute_atmos_command AI tool (CWE-88)#2275

Draft
Copilot wants to merge 5 commits intomainfrom
copilot/fix-unvalidated-subcommand-arguments
Draft

fix: block unvalidated subcommand arguments in execute_atmos_command AI tool (CWE-88)#2275
Copilot wants to merge 5 commits intomainfrom
copilot/fix-unvalidated-subcommand-arguments

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 1, 2026

  • docs/fixes md: Add shell language tag to fenced code block (MD040)
  • execute_command.go – Description: replace "always allowed" with "not blocked by this validator" + reference RequiresPermission/ModePrompt/ModeDeny
  • execute_command.go – Constructor: add permissionModeFromConfig helper; NewExecuteAtmosCommandTool derives mode from config instead of hardcoding ModePrompt
  • execute_command.goisDestructiveAtmosCommand: add firstNonFlag helper that skips both --flag=value (embedded) and --flag value (space-separated) flag tokens to reach real subcommand
  • testmain_test.go: Add _ATMOS_TEST_EXIT_ZERO=1 support for cross-platform success subprocess
  • execute_command_test.go: Replace echo with test binary; add flag-prefix test cases (both forms); add TestPermissionModeFromConfig (6 cases)
  • All 11 tests pass

Copilot AI and others added 3 commits April 1, 2026 13:43
…AI tool (CWE-88)

Agent-Logs-Url: https://github.com/cloudposse/atmos/sessions/2cc53e4b-f449-4919-bbdb-21ae7d47670e

Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com>
… execute_atmos_command

Agent-Logs-Url: https://github.com/cloudposse/atmos/sessions/1d206f86-263e-45b7-a67c-16e65c83ed50

Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com>
… in execute_command

Agent-Logs-Url: https://github.com/cloudposse/atmos/sessions/f1fc3701-122a-467f-9245-42ced8eec31a

Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com>
Copilot AI requested a review from nitrocode April 1, 2026 15:48
@mergify mergify bot added the triage Needs triage label Apr 1, 2026
@nitrocode nitrocode added patch A minor, backward compatible change and removed triage Needs triage labels Apr 1, 2026
@github-actions github-actions bot added the size/m Medium size PR label Apr 1, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 1, 2026

Dependency Review

✅ No vulnerabilities or license issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 07bbbad.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.21%. Comparing base (2c0a3dd) to head (07bbbad).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2275      +/-   ##
==========================================
+ Coverage   77.16%   77.21%   +0.04%     
==========================================
  Files        1034     1034              
  Lines       97542    97632      +90     
==========================================
+ Hits        75271    75384     +113     
+ Misses      18064    18043      -21     
+ Partials     4207     4205       -2     
Flag Coverage Δ
unittests 77.21% <100.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
errors/errors.go 100.00% <ø> (ø)
pkg/ai/tools/atmos/execute_command.go 98.09% <100.00%> (+16.46%) ⬆️

... and 5 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nitrocode
Copy link
Copy Markdown
Member

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

📝 Walkthrough

Walkthrough

This PR introduces security controls to prevent the AI tool from executing destructive Terraform commands without explicit user confirmation. It adds destructive command detection, permission mode gating, a sentinel error, comprehensive test coverage, and documentation of the security fix.

Changes

Cohort / File(s) Summary
Documentation & Error Definition
docs/fixes/2026-04-01-ai-tool-unvalidated-subcommand-arguments.md, errors/errors.go
Added documentation for the security fix (CWE-88) covering prior behavior, mitigations, and test outcomes. Introduced new sentinel error ErrAICommandDestructive to signal blocked destructive commands.
Core Implementation
pkg/ai/tools/atmos/execute_command.go
Added destructive command detection via isDestructiveAtmosCommand() that identifies Terraform state-modifying operations (apply, destroy, import, state ops, workspace ops). Introduced permissionMode field and new constructor NewExecuteAtmosCommandToolWithPermission(). Implemented execution-time control flow that blocks destructive commands unless permission mode is ModePrompt.
Test Coverage
pkg/ai/tools/atmos/execute_command_test.go, pkg/ai/tools/atmos/testmain_test.go
Added comprehensive tests for command classification, whitespace handling, subprocess failures, and permission-gating behavior across different permission modes. Introduced TestMain() for environment-driven test process control.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Tool as ExecuteAtmosCommandTool
    participant Detector as isDestructiveAtmosCommand
    participant Executor as Atmos/Terraform

    Client->>Tool: Execute(command)
    Tool->>Detector: Check if destructive
    Detector-->>Tool: isDestructive (bool)
    
    alt Destructive Command
        Tool->>Tool: Check permissionMode
        alt permissionMode == ModePrompt
            Tool->>Executor: Execute subprocess
            Executor-->>Tool: Result
            Tool-->>Client: Result
        else Other Mode
            Tool-->>Client: ErrAICommandDestructive
        end
    else Safe/Read-only Command
        Tool->>Executor: Execute subprocess
        Executor-->>Tool: Result
        Tool-->>Client: Result
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • osterman
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main change: blocking unvalidated subcommand arguments in the execute_atmos_command AI tool to fix CWE-88.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch copilot/fix-unvalidated-subcommand-arguments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/fixes/2026-04-01-ai-tool-unvalidated-subcommand-arguments.md`:
- Around line 22-26: The fenced code block containing the Terraform/shell
commands needs a language tag to satisfy MD040; update the opening fence from
``` to ```shell (or ```text) so the block is annotated (the block showing
"terraform apply vpc -s prod -var-file=/etc/passwd" / "terraform destroy ..." /
"terraform state rm ...").

In `@pkg/ai/tools/atmos/execute_command_test.go`:
- Around line 232-245: The tests (e.g.,
TestExecuteAtmosCommandTool_DestructiveAllowedInPromptMode and the related tests
around lines 249-289) use the platform-specific "echo" binary which fails on
Windows; replace that with the existing os.Executable()+TestMain test helper or
a fake Command runner so the subprocess is a Go-built test helper executable (or
an injected runner) instead of relying on shell-builtins. Update
NewExecuteAtmosCommandToolWithPermission test setup to set tool.binaryPath to
the helper executable (os.Executable() path) and pass arguments that the
TestMain helper recognizes for the success path, or refactor the tool to accept
an injected Runner interface and use a fake runner in these tests.

In `@pkg/ai/tools/atmos/execute_command.go`:
- Around line 75-81: The comment text incorrectly says safe read-only commands
are "always allowed"; update the description returned by the function that
builds the help/description (the string literal in execute_command.go) to say
these commands are "not blocked by this validator" (or similar) and clarify that
RequiresPermission() still returns true and the global permission checker
(ModePrompt/ModeDeny) will still prompt or deny as appropriate; reference
RequiresPermission(), ModePrompt and ModeDeny in the updated sentence so readers
know the validator only skips blocking them, not bypassing global permission
checks.
- Around line 54-65: The default constructor NewExecuteAtmosCommandTool
currently hard-codes permissionMode to permission.ModePrompt; thread the real
permission into that path by changing NewExecuteAtmosCommandTool to accept a
permission.Mode (or have it call NewExecuteAtmosCommandToolWithPermission) and
set ExecuteAtmosCommandTool.permissionMode from the passed mode, then update the
registration in setup.go to pass the actual runtime permission mode (the
global/checker-provided mode) when constructing the tool so ModeAllow/ModeYOLO
cases are respected; also update NewExecuteAtmosCommandToolWithPermission to
delegate to the new signature to avoid duplication.
- Around line 96-123: isDestructiveAtmosCommand assumes subcommands live at
fixed positions (args[1], args[2]) which breaks when flags precede the
subcommand (e.g., terraform -s prod apply ...); update isDestructiveAtmosCommand
to locate the real subcommand by first confirming args[0] == "terraform" then
skipping any leading flags (tokens that start with "-") to find the first
non-flag token as subCmd, and when handling nested verbs like "state" or
"workspace" similarly advance past any flags to find the nested subcommand (use
the existing destructiveTerraformSubcmds, destructiveTerraformStateSubcmds,
destructiveTerraformWorkspaceSubcmds lookups); add tests for cases like
"terraform -s prod apply vpc" and "terraform --stack prod state rm module.vpc"
to prevent regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 21dd0dba-af53-4c27-8dcc-dd4859b8ebbb

📥 Commits

Reviewing files that changed from the base of the PR and between 2c0a3dd and 9d81419.

📒 Files selected for processing (5)
  • docs/fixes/2026-04-01-ai-tool-unvalidated-subcommand-arguments.md
  • errors/errors.go
  • pkg/ai/tools/atmos/execute_command.go
  • pkg/ai/tools/atmos/execute_command_test.go
  • pkg/ai/tools/atmos/testmain_test.go

Comment on lines +22 to +26
```
terraform apply vpc -s prod -var-file=/etc/passwd
terraform destroy vpc -s prod --auto-approve
terraform state rm module.vpc
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a language tag to this fenced example.

Line 22 currently trips MD040. text or shell is enough here.

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 22-22: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/fixes/2026-04-01-ai-tool-unvalidated-subcommand-arguments.md` around
lines 22 - 26, The fenced code block containing the Terraform/shell commands
needs a language tag to satisfy MD040; update the opening fence from ``` to
```shell (or ```text) so the block is annotated (the block showing "terraform
apply vpc -s prod -var-file=/etc/passwd" / "terraform destroy ..." / "terraform
state rm ...").

Comment on lines +232 to +245
func TestExecuteAtmosCommandTool_DestructiveAllowedInPromptMode(t *testing.T) {
config := &schema.AtmosConfiguration{BasePath: t.TempDir()}
tool := NewExecuteAtmosCommandToolWithPermission(config, permission.ModePrompt)
// Use echo consistent with the existing ValidCommand test in this file.
tool.binaryPath = "echo"

result, err := tool.Execute(context.Background(), map[string]interface{}{
"command": "terraform apply vpc -s prod",
})

require.NoError(t, err)
// echo always succeeds; we just verify the command was NOT blocked by the validator.
assert.True(t, result.Success, "destructive command should not be pre-blocked in ModePrompt")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

echo keeps these new tests non-portable.

exec.Command("echo", ...) works on Unix-like systems but not on Windows, where echo is a shell builtin rather than an executable. Reuse the os.Executable()+TestMain helper for the success path too, or inject a fake runner, so this package stays green on all supported platforms.

As per coding guidelines, "Code must be Linux/macOS/Windows compatible. Use SDKs over binaries. ... Never use bash/shell commands in tests—use Go stdlib (os, filepath, io) for file operations." and "Never use platform-specific binaries in tests (e.g., false, true, sh on Unix). ... Use Go-native test helpers: subprocess via os.Executable() plus TestMain."

Also applies to: 249-289

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/ai/tools/atmos/execute_command_test.go` around lines 232 - 245, The tests
(e.g., TestExecuteAtmosCommandTool_DestructiveAllowedInPromptMode and the
related tests around lines 249-289) use the platform-specific "echo" binary
which fails on Windows; replace that with the existing os.Executable()+TestMain
test helper or a fake Command runner so the subprocess is a Go-built test helper
executable (or an injected runner) instead of relying on shell-builtins. Update
NewExecuteAtmosCommandToolWithPermission test setup to set tool.binaryPath to
the helper executable (os.Executable() path) and pass arguments that the
TestMain helper recognizes for the success path, or refactor the tool to accept
an injected Runner interface and use a fake runner in these tests.

Comment on lines 54 to +65
return &ExecuteAtmosCommandTool{
atmosConfig: atmosConfig,
binaryPath: binary,
atmosConfig: atmosConfig,
binaryPath: binary,
permissionMode: permission.ModePrompt, // default to safest mode
}
}

// NewExecuteAtmosCommandToolWithPermission creates a new Atmos command execution tool with explicit permission mode.
func NewExecuteAtmosCommandToolWithPermission(atmosConfig *schema.AtmosConfiguration, mode permission.Mode) *ExecuteAtmosCommandTool {
t := NewExecuteAtmosCommandTool(atmosConfig)
t.permissionMode = mode
return t
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Thread the real permission mode into the default constructor path.

pkg/ai/tools/atmos/setup.go still registers this tool via NewExecuteAtmosCommandTool(atmosConfig), which hard-codes permissionMode to ModePrompt. In ModeAllow and ModeYOLO, the global checker auto-allows the tool, so destructive commands still skip this new block and execute.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/ai/tools/atmos/execute_command.go` around lines 54 - 65, The default
constructor NewExecuteAtmosCommandTool currently hard-codes permissionMode to
permission.ModePrompt; thread the real permission into that path by changing
NewExecuteAtmosCommandTool to accept a permission.Mode (or have it call
NewExecuteAtmosCommandToolWithPermission) and set
ExecuteAtmosCommandTool.permissionMode from the passed mode, then update the
registration in setup.go to pass the actual runtime permission mode (the
global/checker-provided mode) when constructing the tool so ModeAllow/ModeYOLO
cases are respected; also update NewExecuteAtmosCommandToolWithPermission to
delegate to the new signature to avoid duplication.

Comment on lines +75 to +81
return "LAST RESORT: Execute an Atmos CLI command as a subprocess. " +
"Only use this for commands that do NOT have a dedicated tool. " +
"Do NOT use this for: listing stacks (use atmos_list_stacks), describing components (use atmos_describe_component), " +
"describing affected (use atmos_describe_affected), or validating stacks (use atmos_validate_stacks). " +
"Safe read-only commands (terraform plan, show, output, validate, state list, workspace list, describe, list) are always allowed. " +
"State-modifying operations (terraform apply, destroy, import, force-unlock, state rm/mv/push, workspace new/delete) " +
"require ModePrompt for user confirmation; all other modes block them at the tool layer."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

The description overstates what “always allowed” means.

RequiresPermission() still returns true, and the global checker still prompts in ModePrompt or denies in ModeDeny. Calling safe commands “always allowed” will mislead the model about the actual execution flow; I’d scope this to “not blocked by this validator” instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/ai/tools/atmos/execute_command.go` around lines 75 - 81, The comment text
incorrectly says safe read-only commands are "always allowed"; update the
description returned by the function that builds the help/description (the
string literal in execute_command.go) to say these commands are "not blocked by
this validator" (or similar) and clarify that RequiresPermission() still returns
true and the global permission checker (ModePrompt/ModeDeny) will still prompt
or deny as appropriate; reference RequiresPermission(), ModePrompt and ModeDeny
in the updated sentence so readers know the validator only skips blocking them,
not bypassing global permission checks.

Comment on lines +96 to +123
// isDestructiveAtmosCommand reports whether args represent a state-modifying Terraform operation.
func isDestructiveAtmosCommand(args []string) bool {
if len(args) < 2 {
return false
}

if strings.ToLower(args[0]) != "terraform" {
return false
}

subCmd := strings.ToLower(args[1])

if destructiveTerraformSubcmds[subCmd] {
return true
}

if subCmd == "state" && len(args) >= 3 {
stateSubCmd := strings.ToLower(args[2])
return destructiveTerraformStateSubcmds[stateSubCmd]
}

if subCmd == "workspace" && len(args) >= 3 {
wsSubCmd := strings.ToLower(args[2])
return destructiveTerraformWorkspaceSubcmds[wsSubCmd]
}

return false
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does spf13/cobra accept persistent flags before subcommands, e.g. can terraform -s prod apply vpcorterraform --stack prod state rm module.vpcparse withapply/state as the subcommand after the parent flag?

💡 Result:

Yes, spf13/cobra accepts persistent flags before subcommands. Cobra's parsing algorithm first strips flags from the argument list using the command tree's flags (including persistent flags from parents), then matches the remaining positional arguments to subcommands. This allows persistent flags anywhere before/after the subcommand name, as long as they are valid for the final command path. For example, terraform -s prod apply vpc parses if -s is a persistent flag on root (available to apply), with apply and vpc as args to apply. Similarly for --stack prod state rm module.vpc, assuming --stack is persistent. Terraform uses Cobra and supports global options like -chdir before subcommands, confirming this behavior (e.g., terraform -chdir=foo plan). Issue #10 in Cobra repo fixed early flag parsing issues, enabling this POSIX-style flexibility.

Citations:


🏁 Script executed:

# Find and examine the execute_command.go file
fd execute_command.go -type f

Repository: cloudposse/atmos

Length of output: 230


🏁 Script executed:

# Check how isDestructiveAtmosCommand is called
rg "isDestructiveAtmosCommand" -A 3 -B 3

Repository: cloudposse/atmos

Length of output: 4948


🏁 Script executed:

# Look at cmd/terraform.go to see what persistent flags are defined
head -200 cmd/terraform.go

Repository: cloudposse/atmos

Length of output: 135


🏁 Script executed:

# Read the execute_command.go file
cat -n pkg/ai/tools/atmos/execute_command.go

Repository: cloudposse/atmos

Length of output: 8075


🏁 Script executed:

# Check the test file to understand how args are passed
cat -n pkg/ai/tools/atmos/execute_command_test.go | head -150

Repository: cloudposse/atmos

Length of output: 5757


🏁 Script executed:

# Find terraform command definition
fd terraform.go

Repository: cloudposse/atmos

Length of output: 139


🏁 Script executed:

# Check more test cases to see if there are cases with flags before subcommands
cat -n pkg/ai/tools/atmos/execute_command_test.go | tail -100

Repository: cloudposse/atmos

Length of output: 4199


🏁 Script executed:

# Look for how command strings are constructed - check if there's any parsing/normalization
rg "strings.Fields" pkg/ai/tools/atmos/execute_command.go -B 5 -A 5

Repository: cloudposse/atmos

Length of output: 297


🏁 Script executed:

# Check if there are test cases that would expose the issue
rg "apply\|state rm\|workspace" pkg/ai/tools/atmos/execute_command_test.go -B 2 -A 2 | head -80

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Check documentation or examples for terraform command usage
rg "terraform.*apply\|terraform.*destroy\|terraform.*state" docs/ -l | head -5

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Look at the fix document to see if this was addressed
cat docs/fixes/2026-04-01-ai-tool-unvalidated-subcommand-arguments.md

Repository: cloudposse/atmos

Length of output: 7426


Handle flags preceding subcommands in classification.

The current implementation assumes the destructive subcommand is always at args[1] or args[2], but strings.Fields() does simple whitespace splitting with no flag parsing. Commands like terraform -s prod apply vpc result in args=["terraform", "-s", "prod", "apply", "vpc"], causing isDestructiveAtmosCommand to check args[1]="-s" instead of the actual subcommand at args[3]. This allows destructive operations to bypass the blocker.

Fix by either:

  1. Stripping leading flags before checking subcommand position, or
  2. Reusing the actual CLI parser for the terraform subcommand shape instead of positional assumptions.

Test cases should cover terraform -s prod apply vpc, terraform --stack prod state rm module.vpc, and similar patterns to prevent regression.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/ai/tools/atmos/execute_command.go` around lines 96 - 123,
isDestructiveAtmosCommand assumes subcommands live at fixed positions (args[1],
args[2]) which breaks when flags precede the subcommand (e.g., terraform -s prod
apply ...); update isDestructiveAtmosCommand to locate the real subcommand by
first confirming args[0] == "terraform" then skipping any leading flags (tokens
that start with "-") to find the first non-flag token as subCmd, and when
handling nested verbs like "state" or "workspace" similarly advance past any
flags to find the nested subcommand (use the existing
destructiveTerraformSubcmds, destructiveTerraformStateSubcmds,
destructiveTerraformWorkspaceSubcmds lookups); add tests for cases like
"terraform -s prod apply vpc" and "terraform --stack prod state rm module.vpc"
to prevent regressions.

…ag-skipping, cross-platform tests

Agent-Logs-Url: https://github.com/cloudposse/atmos/sessions/d44c3e97-4198-440c-8f26-c99a314e1d55

Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch A minor, backward compatible change size/m Medium size PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants