diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index e7783e7..9fe166a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -14,7 +14,7 @@ jobs: - "3.13" - "3.12" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v4 - uses: jdx/mise-action@v4 with: tool_versions: | diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index dcf9344..1c2a636 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,7 +10,7 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v4 - uses: jdx/mise-action@v4 - name: Configure Git Credentials run: | diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..de39893 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,3 @@ +# Sentinel Security Journal + +This journal contains critical security learnings specific to this project. diff --git a/project/app.py b/project/app.py index 2225470..8a69213 100644 --- a/project/app.py +++ b/project/app.py @@ -23,6 +23,11 @@ def main(name: str = "World"): Args: name: the name to be greeted """ + # Trim leading and trailing whitespace to sanitize input + name = name.strip() + if not name: + name = "World" + if len(name) > 100: raise UsageError("Invalid name: maximum length is 100 characters.") if not name.isprintable(): diff --git a/tests/test_app.py b/tests/test_app.py index 7c414cd..2bfc48c 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,11 +1,12 @@ from click.testing import CliRunner +from pytest import main -from project.app import main +from project.app import main as app_main def test_help(): runner = CliRunner() - result = runner.invoke(main, ["--help"]) + result = runner.invoke(app_main, ["--help"]) assert result.exit_code == 0 assert "--name " in result.output assert "-V, --version" in result.output @@ -14,31 +15,53 @@ def test_help(): def test_version(): runner = CliRunner() - result = runner.invoke(main, ["-V"]) + result = runner.invoke(app_main, ["-V"]) assert result.exit_code == 0 assert "app, version 0.1.0" in result.output def test_greet(): runner = CliRunner() - result = runner.invoke(main, ["--name", "Jules"]) + result = runner.invoke(app_main, ["--name", "Jules"]) assert result.exit_code == 0 assert "Hello Jules! 👋" in result.output def test_name_too_long(): runner = CliRunner() - result = runner.invoke(main, ["--name", "A" * 101]) + result = runner.invoke(app_main, ["--name", "A" * 101]) assert result.exit_code != 0 assert "maximum length is 100 characters" in result.output def test_name_control_characters(): runner = CliRunner() - result = runner.invoke(main, ["--name", "Injected\x1b[31mRed\x1b[0m"]) + result = runner.invoke(app_main, ["--name", "Injected\x1b[31mRed\x1b[0m"]) assert result.exit_code != 0 assert "control characters are not allowed" in result.output - result = runner.invoke(main, ["--name", "test\x7f"]) + result = runner.invoke(app_main, ["--name", "test\x7f"]) assert result.exit_code != 0 assert "control characters are not allowed" in result.output + + +def test_greet_trimming(): + runner = CliRunner() + result = runner.invoke(app_main, ["--name", " Jules "]) + assert result.exit_code == 0 + assert "Hello Jules! 👋" in result.output + + +def test_greet_empty_fallback(): + runner = CliRunner() + result = runner.invoke(app_main, ["--name", ""]) + assert result.exit_code == 0 + assert "Hello World! 👋" in result.output + + result = runner.invoke(app_main, ["--name", " "]) + assert result.exit_code == 0 + assert "Hello World! 👋" in result.output + + +if __name__ == "__main__": + main()