From bf093221cd5ae4454c01452c145cc1b4a11be94e Mon Sep 17 00:00:00 2001 From: jdalton Date: Sun, 11 Oct 2026 17:37:20 -0400 Subject: [PATCH] fix(scanner): preserve option values --- action.yml | 2 +- docs/parameters.md | 26 +++++++- socket_basics/connectors.yaml | 2 +- .../core/connector/socket_tier1/scanner.py | 52 +++++++++++++--- tests/test_socket_tier1_additional_params.py | 60 +++++++++++++++++++ 5 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 tests/test_socket_tier1_additional_params.py diff --git a/action.yml b/action.yml index 67eeb35..a6d5eee 100644 --- a/action.yml +++ b/action.yml @@ -389,7 +389,7 @@ inputs: required: false default: "" socket_additional_params: - description: "Additional CLI params for 'socket scan reach' (comma or space separated). Also reads SOCKET_ADDITIONAL_PARAMS" + description: "Additional CLI params for 'socket scan reach'. Separate options with spaces; repeat list-valued flags. A comma before another option is also supported. Also reads SOCKET_ADDITIONAL_PARAMS." required: false default: "" socket_tier_1_enabled: diff --git a/docs/parameters.md b/docs/parameters.md index 22adfeb..4da8e10 100644 --- a/docs/parameters.md +++ b/docs/parameters.md @@ -413,13 +413,37 @@ socket-basics --socket-tier1 ``` ### `--socket-additional-params SOCKET_ADDITIONAL_PARAMS` -Additional CLI params for 'socket scan reach' (comma or space separated). +Additional CLI params for `socket scan reach`. Separate options with spaces. +For list-valued CLI options, repeat the flag so each value stays attached to +that option. A legacy comma before another option is also accepted. Commas +inside an argument value stay literal, except an unquoted comma followed by a +hyphen is treated as the legacy separator before another option. Quote the +complete value to pass that sequence literally, such as `"value, --text"`. **Example:** ```bash socket-basics --socket-tier1 --socket-additional-params "--view=full,--all" ``` +To exclude directories from Socket Tier 1 manifest discovery and reachability, +use repeated `--exclude-paths` flags. Paths are relative to the Socket scan +working directory, which defaults to the Basics workspace. `tests` matches only +the `tests` directory at that root; use `**/tests` to match it at any depth. This +option affects the Socket Tier 1 CLI scan. It does not configure other Basics +connectors. + +```bash +socket-basics --socket-tier1 --socket-additional-params="--exclude-paths=tests --exclude-paths=fixtures" +``` + +The same value can be set with `SOCKET_ADDITIONAL_PARAMS` or the GitHub Action's +`socket_additional_params` input: + +```yaml +socket_tier_1_enabled: 'true' +socket_additional_params: '--exclude-paths=tests --exclude-paths=fixtures' +``` + ## Notification Options ### Slack diff --git a/socket_basics/connectors.yaml b/socket_basics/connectors.yaml index b18f461..e83285d 100644 --- a/socket_basics/connectors.yaml +++ b/socket_basics/connectors.yaml @@ -440,7 +440,7 @@ connectors: default: false - name: socket_additional_params option: --socket-additional-params - description: "Additional CLI params for 'socket scan reach' (comma or space separated). Also reads SOCKET_ADDITIONAL_PARAMS" + description: "Additional CLI params for 'socket scan reach'. Separate options with spaces; repeat list-valued flags. A comma before another option is also supported. Also reads SOCKET_ADDITIONAL_PARAMS." env_variable: SOCKET_ADDITIONAL_PARAMS type: str default: "" diff --git a/socket_basics/core/connector/socket_tier1/scanner.py b/socket_basics/core/connector/socket_tier1/scanner.py index 0175460..7dfb661 100644 --- a/socket_basics/core/connector/socket_tier1/scanner.py +++ b/socket_basics/core/connector/socket_tier1/scanner.py @@ -28,6 +28,43 @@ logger = logging.getLogger(__name__) +def _split_legacy_parameter_separators(raw: str) -> List[str]: + separated: List[str] = [] + quote: Optional[str] = None + escaped: bool = False + index: int = 0 + + while index < len(raw): + char = raw[index] + if escaped: + separated.append(char) + escaped = False + elif char == "\\" and quote != "'": + separated.append(char) + escaped = True + elif quote: + separated.append(char) + if char == quote: + quote = None + elif char in ("'", '"'): + separated.append(char) + quote = char + elif char == ",": + next_index = index + 1 + while next_index < len(raw) and raw[next_index].isspace(): + next_index += 1 + if next_index < len(raw) and raw[next_index] == "-": + separated.append(" ") + index = next_index - 1 + else: + separated.append(char) + else: + separated.append(char) + index += 1 + + return shlex.split("".join(separated)) + + class SocketTier1Scanner(BaseConnector): """Runs the Socket CLI reachability analysis and returns the generated .socket.facts.json @@ -89,15 +126,12 @@ def _parse_additional_params(self) -> List[str]: ) if not raw: return [] - # Allow comma-separated or regular shell splitting - if ',' in raw: - parts = [p.strip() for p in raw.split(',') if p.strip()] - # further split any parts that contain spaces - out: List[str] = [] - for p in parts: - out.extend(shlex.split(p)) - return out - return shlex.split(raw) + try: + return _split_legacy_parameter_separators(raw) + except ValueError as error: + raise ConnectorExecutionError( + f"Invalid socket_additional_params: {error}" + ) from error def scan(self) -> Dict[str, Any]: # Check if Socket Tier 1 reachability scan should be skipped (for Node.js Socket CLI integration). diff --git a/tests/test_socket_tier1_additional_params.py b/tests/test_socket_tier1_additional_params.py new file mode 100644 index 0000000..154bb78 --- /dev/null +++ b/tests/test_socket_tier1_additional_params.py @@ -0,0 +1,60 @@ +import pytest + +from socket_basics.core.connector.base import ConnectorExecutionError +from socket_basics.core.connector.socket_tier1.scanner import SocketTier1Scanner + + +@pytest.mark.parametrize( + ("raw", "expected"), + [ + ("", []), + ( + "--exclude-paths=tests --exclude-paths=fixtures", + ["--exclude-paths=tests", "--exclude-paths=fixtures"], + ), + ( + "--exclude-paths tests --exclude-paths fixtures", + ["--exclude-paths", "tests", "--exclude-paths", "fixtures"], + ), + ( + "--exclude-paths='test data'", + ["--exclude-paths=test data"], + ), + ( + '--exclude-paths="tests,fixtures"', + ["--exclude-paths=tests,fixtures"], + ), + ("--exclude-paths=**/tests", ["--exclude-paths=**/tests"]), + ("--view=full,--all", ["--view=full", "--all"]), + ( + "--exclude-paths=tests,fixtures --view=full, --all " + '--exclude-paths "test data, -literal"', + [ + "--exclude-paths=tests,fixtures", + "--view=full", + "--all", + "--exclude-paths", + "test data, -literal", + ], + ), + ( + '--note="quoted, --option text"', + ["--note=quoted, --option text"], + ), + (r"--note=left\, --option", ["--note=left,", "--option"]), + ], +) +def test_parse_additional_params_preserves_argument_boundaries(raw, expected): + scanner = SocketTier1Scanner({"socket_additional_params": raw}) + + assert scanner._parse_additional_params() == expected + + +def test_parse_additional_params_reports_malformed_shell_quotes(): + scanner = SocketTier1Scanner({"socket_additional_params": '--exclude-paths "tests'}) + + with pytest.raises( + ConnectorExecutionError, + match="Invalid socket_additional_params: No closing quotation", + ): + scanner._parse_additional_params()