This repository was archived by the owner on May 18, 2026. It is now read-only.

Remove all uses of eval - #30

Merged
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval
Feb 11, 2026
Merged

Remove all uses of eval#30
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval

Conversation

@zaneduffield

Copy link
Copy Markdown
Contributor

Beyond being risky, many of these uses of eval were actually vulnerable to shell injection.

Switching all cases to nameref variables improves both security and readability, while only raising the minimum bash version from 4 to 4.3.

A simple example of an exploit against the eval-based version follows

#!/bin/bash. cmdarg.sh
declare -a array
declare -A hash
cmdarg 'a:[]''array'
cmdarg_parse "$@"||exit 2

run like

./pwn.sh -a '"; whoami; #'

Beyond being risky, many of these uses of eval were actually vulnerable
to shell injection, if the inputs are untrusted.
Switching all cases to nameref variables improves both security and
readability, while only raising the minimum bash version from 4 to 4.3.
@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

Test look good.

$ PREFIX=/home/andrew/local make test
AK_PREFIX=. /home/andrew/local/bin/shunit.sh -f tunit -t tests | tee tunit.txt
[tests/test_clean_state.sh] shunittest_clean_state .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_subshells .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_usable .... [OK]
[tests/test_dashdash.sh] shunittest_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withbool_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withopt_with_dashdash .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_longopt .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_shortopt .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_and_usage_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_usage_helper .... [OK]
[tests/test_info.sh] shunittest_info_accept_valid .... [OK]
[tests/test_info.sh] shunittest_info_reject_invalid .... [OK]
[tests/test_longopt.sh] shunittest_longopt .... [OK]
[tests/test_longopt.sh] shunittest_longopt_shortopts_still_work .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_array .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_boolean .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_hash .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_string .... [OK]
[tests/test_types.sh] shunittest_array_undefined .... [OK]
[tests/test_types.sh] shunittest_array_values .... [OK]
[tests/test_types.sh] shunittest_boolean_no_optarg .... [OK]
[tests/test_types.sh] shunittest_flags_required .... [OK]
[tests/test_types.sh] shunittest_hash_malformed .... [OK]
[tests/test_types.sh] shunittest_hash_undefined .... [OK]
[tests/test_types.sh] shunittest_hash_values .... [OK]
[tests/test_validators.sh] shunittest_validator_failure_recognized .... [OK]
[tests/test_validators.sh] shunittest_validator_for_array .... [OK]
[tests/test_validators.sh] shunittest_validator_for_hash .... [OK]
==== 30 TESTS in 0 SECONDS : 0 ERRORS, 0 FAILURES ====

@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

For what it's worth I've known about this since the creation of the library, and I'll be honest, I'm not sure why we should worry about the scenario being described here. I see the example, but that's not an example of an exploit, it's just execution of commands. The effect is no different than someone doing

./pwn.sh -a "$(whoami)"

So why is this a cause for alarm? I'm not necessarily against the patch (although even a minor version change might cause problems in some places), I'm just curious about the justification for the concern.

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

In this scenario you're setting the script as the trust boundary, granting permission for the script to be run with some arbitrary arguments, but no more.

In addition to the possibility of injection, the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

@akesterson

akesterson commented Feb 11, 2026

Copy link
Copy Markdown
Owner

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

I understand that scenario, but ensuring the safety of the inputs in that scenario is not cmdarg's job, any more than it's the SQL library's job to ensure that little Bobby Tables is properly handled. The script in this case is running inside of a privileged shell as the user; anything and everything cmdarg does (including the execution of validator functions) will happen with the privileges of the user running the current shell. It is the responsibility of the program calling the library to ensure the library is receiving sane inputs.

... the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

I find this justification a lot more compelling. What are some examples of this behavior?

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

I find this justification a lot more compelling. What are some examples of this behavior?

In my original example the expected and correct behaviour would be for the array variable to be set to contain the provided input, like array=('"; whoami; #').

$ array=('"; whoami; #')
$ echo "${array[@]}"
"; whoami; #

but with eval, the array is actually set as array=('')

@akesterson
akesterson merged commit ac84d37 into akesterson:masterFeb 11, 2026
1 check failed
@akesterson

Copy link
Copy Markdown
Owner

Fair point. Thanks for indulging me.

There is a problem with CI but that doesn't appear related to your changes. The tests look good.

Merged

@zaneduffield
zaneduffield deleted the remove-eval branch February 11, 2026 23:38
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@zaneduffield@akesterson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
This repository was archived by the owner on May 18, 2026. It is now read-only.

Remove all uses of eval - #30

Merged
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval
Feb 11, 2026
Merged

Remove all uses of eval#30
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval

Conversation

@zaneduffield

Copy link
Copy Markdown
Contributor

Beyond being risky, many of these uses of eval were actually vulnerable to shell injection.

Switching all cases to nameref variables improves both security and readability, while only raising the minimum bash version from 4 to 4.3.

A simple example of an exploit against the eval-based version follows

#!/bin/bash. cmdarg.sh
declare -a array
declare -A hash
cmdarg 'a:[]''array'
cmdarg_parse "$@"||exit 2

run like

./pwn.sh -a '"; whoami; #'

Beyond being risky, many of these uses of eval were actually vulnerable
to shell injection, if the inputs are untrusted.
Switching all cases to nameref variables improves both security and
readability, while only raising the minimum bash version from 4 to 4.3.
@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

Test look good.

$ PREFIX=/home/andrew/local make test
AK_PREFIX=. /home/andrew/local/bin/shunit.sh -f tunit -t tests | tee tunit.txt
[tests/test_clean_state.sh] shunittest_clean_state .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_subshells .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_usable .... [OK]
[tests/test_dashdash.sh] shunittest_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withbool_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withopt_with_dashdash .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_longopt .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_shortopt .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_and_usage_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_usage_helper .... [OK]
[tests/test_info.sh] shunittest_info_accept_valid .... [OK]
[tests/test_info.sh] shunittest_info_reject_invalid .... [OK]
[tests/test_longopt.sh] shunittest_longopt .... [OK]
[tests/test_longopt.sh] shunittest_longopt_shortopts_still_work .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_array .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_boolean .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_hash .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_string .... [OK]
[tests/test_types.sh] shunittest_array_undefined .... [OK]
[tests/test_types.sh] shunittest_array_values .... [OK]
[tests/test_types.sh] shunittest_boolean_no_optarg .... [OK]
[tests/test_types.sh] shunittest_flags_required .... [OK]
[tests/test_types.sh] shunittest_hash_malformed .... [OK]
[tests/test_types.sh] shunittest_hash_undefined .... [OK]
[tests/test_types.sh] shunittest_hash_values .... [OK]
[tests/test_validators.sh] shunittest_validator_failure_recognized .... [OK]
[tests/test_validators.sh] shunittest_validator_for_array .... [OK]
[tests/test_validators.sh] shunittest_validator_for_hash .... [OK]
==== 30 TESTS in 0 SECONDS : 0 ERRORS, 0 FAILURES ====

@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

For what it's worth I've known about this since the creation of the library, and I'll be honest, I'm not sure why we should worry about the scenario being described here. I see the example, but that's not an example of an exploit, it's just execution of commands. The effect is no different than someone doing

./pwn.sh -a "$(whoami)"

So why is this a cause for alarm? I'm not necessarily against the patch (although even a minor version change might cause problems in some places), I'm just curious about the justification for the concern.

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

In this scenario you're setting the script as the trust boundary, granting permission for the script to be run with some arbitrary arguments, but no more.

In addition to the possibility of injection, the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

@akesterson

akesterson commented Feb 11, 2026

Copy link
Copy Markdown
Owner

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

I understand that scenario, but ensuring the safety of the inputs in that scenario is not cmdarg's job, any more than it's the SQL library's job to ensure that little Bobby Tables is properly handled. The script in this case is running inside of a privileged shell as the user; anything and everything cmdarg does (including the execution of validator functions) will happen with the privileges of the user running the current shell. It is the responsibility of the program calling the library to ensure the library is receiving sane inputs.

... the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

I find this justification a lot more compelling. What are some examples of this behavior?

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

I find this justification a lot more compelling. What are some examples of this behavior?

In my original example the expected and correct behaviour would be for the array variable to be set to contain the provided input, like array=('"; whoami; #').

$ array=('"; whoami; #')
$ echo "${array[@]}"
"; whoami; #

but with eval, the array is actually set as array=('')

@akesterson
akesterson merged commit ac84d37 into akesterson:masterFeb 11, 2026
1 check failed
@akesterson

Copy link
Copy Markdown
Owner

Fair point. Thanks for indulging me.

There is a problem with CI but that doesn't appear related to your changes. The tests look good.

Merged

@zaneduffield
zaneduffield deleted the remove-eval branch February 11, 2026 23:38
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@zaneduffield@akesterson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on May 18, 2026. It is now read-only.

Remove all uses of eval - #30

Merged
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval
Feb 11, 2026
Merged

Remove all uses of eval#30
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval

Conversation

@zaneduffield

Copy link
Copy Markdown
Contributor

Beyond being risky, many of these uses of eval were actually vulnerable to shell injection.

Switching all cases to nameref variables improves both security and readability, while only raising the minimum bash version from 4 to 4.3.

A simple example of an exploit against the eval-based version follows

#!/bin/bash. cmdarg.sh
declare -a array
declare -A hash
cmdarg 'a:[]''array'
cmdarg_parse "$@"||exit 2

run like

./pwn.sh -a '"; whoami; #'

Beyond being risky, many of these uses of eval were actually vulnerable
to shell injection, if the inputs are untrusted.
Switching all cases to nameref variables improves both security and
readability, while only raising the minimum bash version from 4 to 4.3.
@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

Test look good.

$ PREFIX=/home/andrew/local make test
AK_PREFIX=. /home/andrew/local/bin/shunit.sh -f tunit -t tests | tee tunit.txt
[tests/test_clean_state.sh] shunittest_clean_state .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_subshells .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_usable .... [OK]
[tests/test_dashdash.sh] shunittest_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withbool_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withopt_with_dashdash .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_longopt .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_shortopt .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_and_usage_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_usage_helper .... [OK]
[tests/test_info.sh] shunittest_info_accept_valid .... [OK]
[tests/test_info.sh] shunittest_info_reject_invalid .... [OK]
[tests/test_longopt.sh] shunittest_longopt .... [OK]
[tests/test_longopt.sh] shunittest_longopt_shortopts_still_work .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_array .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_boolean .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_hash .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_string .... [OK]
[tests/test_types.sh] shunittest_array_undefined .... [OK]
[tests/test_types.sh] shunittest_array_values .... [OK]
[tests/test_types.sh] shunittest_boolean_no_optarg .... [OK]
[tests/test_types.sh] shunittest_flags_required .... [OK]
[tests/test_types.sh] shunittest_hash_malformed .... [OK]
[tests/test_types.sh] shunittest_hash_undefined .... [OK]
[tests/test_types.sh] shunittest_hash_values .... [OK]
[tests/test_validators.sh] shunittest_validator_failure_recognized .... [OK]
[tests/test_validators.sh] shunittest_validator_for_array .... [OK]
[tests/test_validators.sh] shunittest_validator_for_hash .... [OK]
==== 30 TESTS in 0 SECONDS : 0 ERRORS, 0 FAILURES ====

@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

For what it's worth I've known about this since the creation of the library, and I'll be honest, I'm not sure why we should worry about the scenario being described here. I see the example, but that's not an example of an exploit, it's just execution of commands. The effect is no different than someone doing

./pwn.sh -a "$(whoami)"

So why is this a cause for alarm? I'm not necessarily against the patch (although even a minor version change might cause problems in some places), I'm just curious about the justification for the concern.

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

In this scenario you're setting the script as the trust boundary, granting permission for the script to be run with some arbitrary arguments, but no more.

In addition to the possibility of injection, the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

@akesterson

akesterson commented Feb 11, 2026

Copy link
Copy Markdown
Owner

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

I understand that scenario, but ensuring the safety of the inputs in that scenario is not cmdarg's job, any more than it's the SQL library's job to ensure that little Bobby Tables is properly handled. The script in this case is running inside of a privileged shell as the user; anything and everything cmdarg does (including the execution of validator functions) will happen with the privileges of the user running the current shell. It is the responsibility of the program calling the library to ensure the library is receiving sane inputs.

... the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

I find this justification a lot more compelling. What are some examples of this behavior?

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

I find this justification a lot more compelling. What are some examples of this behavior?

In my original example the expected and correct behaviour would be for the array variable to be set to contain the provided input, like array=('"; whoami; #').

$ array=('"; whoami; #')
$ echo "${array[@]}"
"; whoami; #

but with eval, the array is actually set as array=('')

@akesterson
akesterson merged commit ac84d37 into akesterson:masterFeb 11, 2026
1 check failed
@akesterson

Copy link
Copy Markdown
Owner

Fair point. Thanks for indulging me.

There is a problem with CI but that doesn't appear related to your changes. The tests look good.

Merged

@zaneduffield
zaneduffield deleted the remove-eval branch February 11, 2026 23:38
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@zaneduffield@akesterson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on May 18, 2026. It is now read-only.

Remove all uses of eval - #30

Merged
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval
Feb 11, 2026
Merged

Remove all uses of eval#30
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval

Conversation

@zaneduffield

Copy link
Copy Markdown
Contributor

Beyond being risky, many of these uses of eval were actually vulnerable to shell injection.

Switching all cases to nameref variables improves both security and readability, while only raising the minimum bash version from 4 to 4.3.

A simple example of an exploit against the eval-based version follows

#!/bin/bash. cmdarg.sh
declare -a array
declare -A hash
cmdarg 'a:[]''array'
cmdarg_parse "$@"||exit 2

run like

./pwn.sh -a '"; whoami; #'

Beyond being risky, many of these uses of eval were actually vulnerable
to shell injection, if the inputs are untrusted.
Switching all cases to nameref variables improves both security and
readability, while only raising the minimum bash version from 4 to 4.3.
@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

Test look good.

$ PREFIX=/home/andrew/local make test
AK_PREFIX=. /home/andrew/local/bin/shunit.sh -f tunit -t tests | tee tunit.txt
[tests/test_clean_state.sh] shunittest_clean_state .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_subshells .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_usable .... [OK]
[tests/test_dashdash.sh] shunittest_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withbool_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withopt_with_dashdash .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_longopt .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_shortopt .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_and_usage_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_usage_helper .... [OK]
[tests/test_info.sh] shunittest_info_accept_valid .... [OK]
[tests/test_info.sh] shunittest_info_reject_invalid .... [OK]
[tests/test_longopt.sh] shunittest_longopt .... [OK]
[tests/test_longopt.sh] shunittest_longopt_shortopts_still_work .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_array .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_boolean .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_hash .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_string .... [OK]
[tests/test_types.sh] shunittest_array_undefined .... [OK]
[tests/test_types.sh] shunittest_array_values .... [OK]
[tests/test_types.sh] shunittest_boolean_no_optarg .... [OK]
[tests/test_types.sh] shunittest_flags_required .... [OK]
[tests/test_types.sh] shunittest_hash_malformed .... [OK]
[tests/test_types.sh] shunittest_hash_undefined .... [OK]
[tests/test_types.sh] shunittest_hash_values .... [OK]
[tests/test_validators.sh] shunittest_validator_failure_recognized .... [OK]
[tests/test_validators.sh] shunittest_validator_for_array .... [OK]
[tests/test_validators.sh] shunittest_validator_for_hash .... [OK]
==== 30 TESTS in 0 SECONDS : 0 ERRORS, 0 FAILURES ====

@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

For what it's worth I've known about this since the creation of the library, and I'll be honest, I'm not sure why we should worry about the scenario being described here. I see the example, but that's not an example of an exploit, it's just execution of commands. The effect is no different than someone doing

./pwn.sh -a "$(whoami)"

So why is this a cause for alarm? I'm not necessarily against the patch (although even a minor version change might cause problems in some places), I'm just curious about the justification for the concern.

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

In this scenario you're setting the script as the trust boundary, granting permission for the script to be run with some arbitrary arguments, but no more.

In addition to the possibility of injection, the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

@akesterson

akesterson commented Feb 11, 2026

Copy link
Copy Markdown
Owner

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

I understand that scenario, but ensuring the safety of the inputs in that scenario is not cmdarg's job, any more than it's the SQL library's job to ensure that little Bobby Tables is properly handled. The script in this case is running inside of a privileged shell as the user; anything and everything cmdarg does (including the execution of validator functions) will happen with the privileges of the user running the current shell. It is the responsibility of the program calling the library to ensure the library is receiving sane inputs.

... the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

I find this justification a lot more compelling. What are some examples of this behavior?

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

I find this justification a lot more compelling. What are some examples of this behavior?

In my original example the expected and correct behaviour would be for the array variable to be set to contain the provided input, like array=('"; whoami; #').

$ array=('"; whoami; #')
$ echo "${array[@]}"
"; whoami; #

but with eval, the array is actually set as array=('')

@akesterson
akesterson merged commit ac84d37 into akesterson:masterFeb 11, 2026
1 check failed
@akesterson

Copy link
Copy Markdown
Owner

Fair point. Thanks for indulging me.

There is a problem with CI but that doesn't appear related to your changes. The tests look good.

Merged

@zaneduffield
zaneduffield deleted the remove-eval branch February 11, 2026 23:38
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@zaneduffield@akesterson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
This repository was archived by the owner on May 18, 2026. It is now read-only.

Remove all uses of eval - #30

Merged
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval
Feb 11, 2026
Merged

Remove all uses of eval#30
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval

Conversation

@zaneduffield

Copy link
Copy Markdown
Contributor

Beyond being risky, many of these uses of eval were actually vulnerable to shell injection.

Switching all cases to nameref variables improves both security and readability, while only raising the minimum bash version from 4 to 4.3.

A simple example of an exploit against the eval-based version follows

#!/bin/bash. cmdarg.sh
declare -a array
declare -A hash
cmdarg 'a:[]''array'
cmdarg_parse "$@"||exit 2

run like

./pwn.sh -a '"; whoami; #'

Beyond being risky, many of these uses of eval were actually vulnerable
to shell injection, if the inputs are untrusted.
Switching all cases to nameref variables improves both security and
readability, while only raising the minimum bash version from 4 to 4.3.
@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

Test look good.

$ PREFIX=/home/andrew/local make test
AK_PREFIX=. /home/andrew/local/bin/shunit.sh -f tunit -t tests | tee tunit.txt
[tests/test_clean_state.sh] shunittest_clean_state .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_subshells .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_usable .... [OK]
[tests/test_dashdash.sh] shunittest_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withbool_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withopt_with_dashdash .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_longopt .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_shortopt .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_and_usage_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_usage_helper .... [OK]
[tests/test_info.sh] shunittest_info_accept_valid .... [OK]
[tests/test_info.sh] shunittest_info_reject_invalid .... [OK]
[tests/test_longopt.sh] shunittest_longopt .... [OK]
[tests/test_longopt.sh] shunittest_longopt_shortopts_still_work .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_array .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_boolean .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_hash .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_string .... [OK]
[tests/test_types.sh] shunittest_array_undefined .... [OK]
[tests/test_types.sh] shunittest_array_values .... [OK]
[tests/test_types.sh] shunittest_boolean_no_optarg .... [OK]
[tests/test_types.sh] shunittest_flags_required .... [OK]
[tests/test_types.sh] shunittest_hash_malformed .... [OK]
[tests/test_types.sh] shunittest_hash_undefined .... [OK]
[tests/test_types.sh] shunittest_hash_values .... [OK]
[tests/test_validators.sh] shunittest_validator_failure_recognized .... [OK]
[tests/test_validators.sh] shunittest_validator_for_array .... [OK]
[tests/test_validators.sh] shunittest_validator_for_hash .... [OK]
==== 30 TESTS in 0 SECONDS : 0 ERRORS, 0 FAILURES ====

@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

For what it's worth I've known about this since the creation of the library, and I'll be honest, I'm not sure why we should worry about the scenario being described here. I see the example, but that's not an example of an exploit, it's just execution of commands. The effect is no different than someone doing

./pwn.sh -a "$(whoami)"

So why is this a cause for alarm? I'm not necessarily against the patch (although even a minor version change might cause problems in some places), I'm just curious about the justification for the concern.

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

In this scenario you're setting the script as the trust boundary, granting permission for the script to be run with some arbitrary arguments, but no more.

In addition to the possibility of injection, the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

@akesterson

akesterson commented Feb 11, 2026

Copy link
Copy Markdown
Owner

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

I understand that scenario, but ensuring the safety of the inputs in that scenario is not cmdarg's job, any more than it's the SQL library's job to ensure that little Bobby Tables is properly handled. The script in this case is running inside of a privileged shell as the user; anything and everything cmdarg does (including the execution of validator functions) will happen with the privileges of the user running the current shell. It is the responsibility of the program calling the library to ensure the library is receiving sane inputs.

... the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

I find this justification a lot more compelling. What are some examples of this behavior?

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

I find this justification a lot more compelling. What are some examples of this behavior?

In my original example the expected and correct behaviour would be for the array variable to be set to contain the provided input, like array=('"; whoami; #').

$ array=('"; whoami; #')
$ echo "${array[@]}"
"; whoami; #

but with eval, the array is actually set as array=('')

@akesterson
akesterson merged commit ac84d37 into akesterson:masterFeb 11, 2026
1 check failed
@akesterson

Copy link
Copy Markdown
Owner

Fair point. Thanks for indulging me.

There is a problem with CI but that doesn't appear related to your changes. The tests look good.

Merged

@zaneduffield
zaneduffield deleted the remove-eval branch February 11, 2026 23:38
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@zaneduffield@akesterson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on May 18, 2026. It is now read-only.

Remove all uses of eval - #30

Merged
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval
Feb 11, 2026
Merged

Remove all uses of eval#30
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval

Conversation

@zaneduffield

Copy link
Copy Markdown
Contributor

Beyond being risky, many of these uses of eval were actually vulnerable to shell injection.

Switching all cases to nameref variables improves both security and readability, while only raising the minimum bash version from 4 to 4.3.

A simple example of an exploit against the eval-based version follows

#!/bin/bash. cmdarg.sh
declare -a array
declare -A hash
cmdarg 'a:[]''array'
cmdarg_parse "$@"||exit 2

run like

./pwn.sh -a '"; whoami; #'

Beyond being risky, many of these uses of eval were actually vulnerable
to shell injection, if the inputs are untrusted.
Switching all cases to nameref variables improves both security and
readability, while only raising the minimum bash version from 4 to 4.3.
@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

Test look good.

$ PREFIX=/home/andrew/local make test
AK_PREFIX=. /home/andrew/local/bin/shunit.sh -f tunit -t tests | tee tunit.txt
[tests/test_clean_state.sh] shunittest_clean_state .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_subshells .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_usable .... [OK]
[tests/test_dashdash.sh] shunittest_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withbool_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withopt_with_dashdash .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_longopt .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_shortopt .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_and_usage_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_usage_helper .... [OK]
[tests/test_info.sh] shunittest_info_accept_valid .... [OK]
[tests/test_info.sh] shunittest_info_reject_invalid .... [OK]
[tests/test_longopt.sh] shunittest_longopt .... [OK]
[tests/test_longopt.sh] shunittest_longopt_shortopts_still_work .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_array .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_boolean .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_hash .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_string .... [OK]
[tests/test_types.sh] shunittest_array_undefined .... [OK]
[tests/test_types.sh] shunittest_array_values .... [OK]
[tests/test_types.sh] shunittest_boolean_no_optarg .... [OK]
[tests/test_types.sh] shunittest_flags_required .... [OK]
[tests/test_types.sh] shunittest_hash_malformed .... [OK]
[tests/test_types.sh] shunittest_hash_undefined .... [OK]
[tests/test_types.sh] shunittest_hash_values .... [OK]
[tests/test_validators.sh] shunittest_validator_failure_recognized .... [OK]
[tests/test_validators.sh] shunittest_validator_for_array .... [OK]
[tests/test_validators.sh] shunittest_validator_for_hash .... [OK]
==== 30 TESTS in 0 SECONDS : 0 ERRORS, 0 FAILURES ====

@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

For what it's worth I've known about this since the creation of the library, and I'll be honest, I'm not sure why we should worry about the scenario being described here. I see the example, but that's not an example of an exploit, it's just execution of commands. The effect is no different than someone doing

./pwn.sh -a "$(whoami)"

So why is this a cause for alarm? I'm not necessarily against the patch (although even a minor version change might cause problems in some places), I'm just curious about the justification for the concern.

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

In this scenario you're setting the script as the trust boundary, granting permission for the script to be run with some arbitrary arguments, but no more.

In addition to the possibility of injection, the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

@akesterson

akesterson commented Feb 11, 2026

Copy link
Copy Markdown
Owner

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

I understand that scenario, but ensuring the safety of the inputs in that scenario is not cmdarg's job, any more than it's the SQL library's job to ensure that little Bobby Tables is properly handled. The script in this case is running inside of a privileged shell as the user; anything and everything cmdarg does (including the execution of validator functions) will happen with the privileges of the user running the current shell. It is the responsibility of the program calling the library to ensure the library is receiving sane inputs.

... the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

I find this justification a lot more compelling. What are some examples of this behavior?

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

I find this justification a lot more compelling. What are some examples of this behavior?

In my original example the expected and correct behaviour would be for the array variable to be set to contain the provided input, like array=('"; whoami; #').

$ array=('"; whoami; #')
$ echo "${array[@]}"
"; whoami; #

but with eval, the array is actually set as array=('')

@akesterson
akesterson merged commit ac84d37 into akesterson:masterFeb 11, 2026
1 check failed
@akesterson

Copy link
Copy Markdown
Owner

Fair point. Thanks for indulging me.

There is a problem with CI but that doesn't appear related to your changes. The tests look good.

Merged

@zaneduffield
zaneduffield deleted the remove-eval branch February 11, 2026 23:38
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@zaneduffield@akesterson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on May 18, 2026. It is now read-only.

Remove all uses of eval - #30

Merged
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval
Feb 11, 2026
Merged

Remove all uses of eval#30
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval

Conversation

@zaneduffield

Copy link
Copy Markdown
Contributor

Beyond being risky, many of these uses of eval were actually vulnerable to shell injection.

Switching all cases to nameref variables improves both security and readability, while only raising the minimum bash version from 4 to 4.3.

A simple example of an exploit against the eval-based version follows

#!/bin/bash. cmdarg.sh
declare -a array
declare -A hash
cmdarg 'a:[]''array'
cmdarg_parse "$@"||exit 2

run like

./pwn.sh -a '"; whoami; #'

Beyond being risky, many of these uses of eval were actually vulnerable
to shell injection, if the inputs are untrusted.
Switching all cases to nameref variables improves both security and
readability, while only raising the minimum bash version from 4 to 4.3.
@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

Test look good.

$ PREFIX=/home/andrew/local make test
AK_PREFIX=. /home/andrew/local/bin/shunit.sh -f tunit -t tests | tee tunit.txt
[tests/test_clean_state.sh] shunittest_clean_state .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_subshells .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_usable .... [OK]
[tests/test_dashdash.sh] shunittest_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withbool_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withopt_with_dashdash .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_longopt .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_shortopt .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_and_usage_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_usage_helper .... [OK]
[tests/test_info.sh] shunittest_info_accept_valid .... [OK]
[tests/test_info.sh] shunittest_info_reject_invalid .... [OK]
[tests/test_longopt.sh] shunittest_longopt .... [OK]
[tests/test_longopt.sh] shunittest_longopt_shortopts_still_work .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_array .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_boolean .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_hash .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_string .... [OK]
[tests/test_types.sh] shunittest_array_undefined .... [OK]
[tests/test_types.sh] shunittest_array_values .... [OK]
[tests/test_types.sh] shunittest_boolean_no_optarg .... [OK]
[tests/test_types.sh] shunittest_flags_required .... [OK]
[tests/test_types.sh] shunittest_hash_malformed .... [OK]
[tests/test_types.sh] shunittest_hash_undefined .... [OK]
[tests/test_types.sh] shunittest_hash_values .... [OK]
[tests/test_validators.sh] shunittest_validator_failure_recognized .... [OK]
[tests/test_validators.sh] shunittest_validator_for_array .... [OK]
[tests/test_validators.sh] shunittest_validator_for_hash .... [OK]
==== 30 TESTS in 0 SECONDS : 0 ERRORS, 0 FAILURES ====

@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

For what it's worth I've known about this since the creation of the library, and I'll be honest, I'm not sure why we should worry about the scenario being described here. I see the example, but that's not an example of an exploit, it's just execution of commands. The effect is no different than someone doing

./pwn.sh -a "$(whoami)"

So why is this a cause for alarm? I'm not necessarily against the patch (although even a minor version change might cause problems in some places), I'm just curious about the justification for the concern.

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

In this scenario you're setting the script as the trust boundary, granting permission for the script to be run with some arbitrary arguments, but no more.

In addition to the possibility of injection, the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

@akesterson

akesterson commented Feb 11, 2026

Copy link
Copy Markdown
Owner

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

I understand that scenario, but ensuring the safety of the inputs in that scenario is not cmdarg's job, any more than it's the SQL library's job to ensure that little Bobby Tables is properly handled. The script in this case is running inside of a privileged shell as the user; anything and everything cmdarg does (including the execution of validator functions) will happen with the privileges of the user running the current shell. It is the responsibility of the program calling the library to ensure the library is receiving sane inputs.

... the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

I find this justification a lot more compelling. What are some examples of this behavior?

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

I find this justification a lot more compelling. What are some examples of this behavior?

In my original example the expected and correct behaviour would be for the array variable to be set to contain the provided input, like array=('"; whoami; #').

$ array=('"; whoami; #')
$ echo "${array[@]}"
"; whoami; #

but with eval, the array is actually set as array=('')

@akesterson
akesterson merged commit ac84d37 into akesterson:masterFeb 11, 2026
1 check failed
@akesterson

Copy link
Copy Markdown
Owner

Fair point. Thanks for indulging me.

There is a problem with CI but that doesn't appear related to your changes. The tests look good.

Merged

@zaneduffield
zaneduffield deleted the remove-eval branch February 11, 2026 23:38
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@zaneduffield@akesterson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
This repository was archived by the owner on May 18, 2026. It is now read-only.

Remove all uses of eval - #30

Merged
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval
Feb 11, 2026
Merged

Remove all uses of eval#30
akesterson merged 2 commits into
akesterson:masterfrom
zaneduffield:remove-eval

Conversation

@zaneduffield

Copy link
Copy Markdown
Contributor

Beyond being risky, many of these uses of eval were actually vulnerable to shell injection.

Switching all cases to nameref variables improves both security and readability, while only raising the minimum bash version from 4 to 4.3.

A simple example of an exploit against the eval-based version follows

#!/bin/bash. cmdarg.sh
declare -a array
declare -A hash
cmdarg 'a:[]''array'
cmdarg_parse "$@"||exit 2

run like

./pwn.sh -a '"; whoami; #'

Beyond being risky, many of these uses of eval were actually vulnerable
to shell injection, if the inputs are untrusted.
Switching all cases to nameref variables improves both security and
readability, while only raising the minimum bash version from 4 to 4.3.
@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

Test look good.

$ PREFIX=/home/andrew/local make test
AK_PREFIX=. /home/andrew/local/bin/shunit.sh -f tunit -t tests | tee tunit.txt
[tests/test_clean_state.sh] shunittest_clean_state .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_subshells .... [OK]
[tests/test_clean_state.sh] shunittest_clean_state_usable .... [OK]
[tests/test_dashdash.sh] shunittest_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withbool_missing_dashdash .... [OK]
[tests/test_dashdash.sh] shunittest_withopt_with_dashdash .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_longopt .... [OK]
[tests/test_equals.sh] shunittest_test_equals_parsing_shortopt .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_and_usage_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_describe_helper .... [OK]
[tests/test_helpers.sh] shunittest_test_usage_helper .... [OK]
[tests/test_info.sh] shunittest_info_accept_valid .... [OK]
[tests/test_info.sh] shunittest_info_reject_invalid .... [OK]
[tests/test_longopt.sh] shunittest_longopt .... [OK]
[tests/test_longopt.sh] shunittest_longopt_shortopts_still_work .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_array .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_boolean .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_hash .... [OK]
[tests/test_longopt.sh] shunittest_longopt_usage_messages_string .... [OK]
[tests/test_types.sh] shunittest_array_undefined .... [OK]
[tests/test_types.sh] shunittest_array_values .... [OK]
[tests/test_types.sh] shunittest_boolean_no_optarg .... [OK]
[tests/test_types.sh] shunittest_flags_required .... [OK]
[tests/test_types.sh] shunittest_hash_malformed .... [OK]
[tests/test_types.sh] shunittest_hash_undefined .... [OK]
[tests/test_types.sh] shunittest_hash_values .... [OK]
[tests/test_validators.sh] shunittest_validator_failure_recognized .... [OK]
[tests/test_validators.sh] shunittest_validator_for_array .... [OK]
[tests/test_validators.sh] shunittest_validator_for_hash .... [OK]
==== 30 TESTS in 0 SECONDS : 0 ERRORS, 0 FAILURES ====

@akesterson

akesterson commented Feb 10, 2026

Copy link
Copy Markdown
Owner

For what it's worth I've known about this since the creation of the library, and I'll be honest, I'm not sure why we should worry about the scenario being described here. I see the example, but that's not an example of an exploit, it's just execution of commands. The effect is no different than someone doing

./pwn.sh -a "$(whoami)"

So why is this a cause for alarm? I'm not necessarily against the patch (although even a minor version change might cause problems in some places), I'm just curious about the justification for the concern.

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

In this scenario you're setting the script as the trust boundary, granting permission for the script to be run with some arbitrary arguments, but no more.

In addition to the possibility of injection, the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

@akesterson

akesterson commented Feb 11, 2026

Copy link
Copy Markdown
Owner

Imagine a scenario where the script is hooked into some automation process, where the library is used to parse the arguments provided from some untrusted source.

I understand that scenario, but ensuring the safety of the inputs in that scenario is not cmdarg's job, any more than it's the SQL library's job to ensure that little Bobby Tables is properly handled. The script in this case is running inside of a privileged shell as the user; anything and everything cmdarg does (including the execution of validator functions) will happen with the privileges of the user running the current shell. It is the responsibility of the program calling the library to ensure the library is receiving sane inputs.

... the version with eval is simply less correct, in that there are valid strings you can provide that it cannot parse faithfully.

I find this justification a lot more compelling. What are some examples of this behavior?

@zaneduffield

Copy link
Copy Markdown
ContributorAuthor

I find this justification a lot more compelling. What are some examples of this behavior?

In my original example the expected and correct behaviour would be for the array variable to be set to contain the provided input, like array=('"; whoami; #').

$ array=('"; whoami; #')
$ echo "${array[@]}"
"; whoami; #

but with eval, the array is actually set as array=('')

@akesterson
akesterson merged commit ac84d37 into akesterson:masterFeb 11, 2026
1 check failed
@akesterson

Copy link
Copy Markdown
Owner

Fair point. Thanks for indulging me.

There is a problem with CI but that doesn't appear related to your changes. The tests look good.

Merged

@zaneduffield
zaneduffield deleted the remove-eval branch February 11, 2026 23:38
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@zaneduffield@akesterson