Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
branches: [main]
pull_request:
branches: [main]
release:
types: [published]

jobs:
test:
Expand Down Expand Up @@ -41,3 +43,31 @@ jobs:

- name: Build wheel
run: python -m build --wheel

pypi-publish:
name: Upload release to PyPI
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'release'
environment:
name: pypi
url: https://pypi.org/p/trushell
permissions:
id-token: write

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install build tools
run: python -m pip install --upgrade build

- name: Build package
run: python -m build

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
Comment on lines +59 to +73

Copy link
Copy Markdown

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

🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/python-ci.yml"
echo "== File: $FILE =="
nl -ba "$FILE" | sed -n '1,140p'

Repository: TruFoundation/TruShell

Length of output: 171


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/python-ci.yml"
echo "== File: $FILE =="
cat -n "$FILE" | sed -n '45,95p'

Repository: TruFoundation/TruShell

Length of output: 968


🌐 Web query:

actions/checkout persist-credentials default value

💡 Result:

The default value for the persist-credentials input in the actions/checkout GitHub Action is true [1][2][3]. When set to true (the default), the action configures the authentication token or SSH key with the local git configuration [1][4]. This allows subsequent steps in your workflow to run authenticated git commands (such as git fetch or git push) automatically [2][5]. The action ensures security by removing these credentials during the post-job cleanup phase [2][5]. If you wish to disable this behavior, you can explicitly set persist-credentials to false [2][4].

Citations:


Pin actions to commit SHAs and disable checkout credential persistence in the PyPI release publish job

  • The publish job uses floating action refs (actions/checkout@v4, actions/setup-python@v5, pypa/gh-action-pypi-publish@release/v1), weakening supply-chain guarantees.
  • actions/checkout@v4 is run without persist-credentials: false (default is true), so checkout auth credentials may persist beyond what the build/publish steps require.
Suggested hardening
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@<full_sha_for_v4>
+        with:
+          persist-credentials: false
@@
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@<full_sha_for_v5>
@@
-        uses: pypa/gh-action-pypi-publish@release/v1
+        uses: pypa/gh-action-pypi-publish@<full_sha_for_release_v1>
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 57-57: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 57-57: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 60-60: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 71-71: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/python-ci.yml around lines 57 - 71, Replace floating
action refs with pinned commit SHAs for actions/checkout, actions/setup-python,
and pypa/gh-action-pypi-publish used in the Publish to PyPI job; specifically
update the checkout step (currently using actions/checkout@v4) to include
persist-credentials: false and point to the official v4 commit SHA, and pin
actions/setup-python@v5 and pypa/gh-action-pypi-publish@release/v1 to their
respective commit SHAs so the Build package and Publish to PyPI steps use
immutable action references.

Source: Linters/SAST tools

32 changes: 0 additions & 32 deletions .github/workflows/workflow.yml

This file was deleted.

6 changes: 6 additions & 0 deletions tests/test_data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import re
import sys
from pathlib import Path

import pytest

def _strip_ansi(text: str) -> str:
return re.sub(r"\x1b\[[0-9;]*m", "", text)

Expand All @@ -13,6 +16,7 @@ def test_run_csv_view_file_not_found() -> None:
assert "not found." in output


@pytest.mark.skipif(sys.platform == "win32", reason="shlex.split() mangles Windows backslash paths, causing file-not-found errors")
def test_run_csv_view_empty_file(tmp_path: Path) -> None:
from trushell.commands.data import run_csv_view

Expand All @@ -23,6 +27,7 @@ def test_run_csv_view_empty_file(tmp_path: Path) -> None:
assert "Warning: File is empty." in output


@pytest.mark.skipif(sys.platform == "win32", reason="shlex.split() mangles Windows backslash paths, causing file-not-found errors")
def test_run_csv_view_shows_limited_rows(tmp_path: Path) -> None:
from trushell.commands.data import run_csv_view

Expand All @@ -41,6 +46,7 @@ def test_run_csv_view_shows_limited_rows(tmp_path: Path) -> None:
assert "...and 3 more rows" in output


@pytest.mark.skipif(sys.platform == "win32", reason="shlex.split() mangles Windows backslash paths, causing file-not-found errors")
def test_run_csv_view_short_rows_are_padded(tmp_path: Path) -> None:
"""Ensure ragged rows are padded with empty cells.

Expand Down
32 changes: 31 additions & 1 deletion tests/test_help_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@
from types import SimpleNamespace

from trushell.commands.core import run_help
from trushell.cli import _handle_local_command


def test_run_help_prints_docstring_for_known_command(monkeypatch, capsys):
import importlib

fake_kernel = SimpleNamespace(
registry={
"settings": {
"path": "trushell/commands/settings.py",
"function": "run_settings",
}
}
},
_import_module=lambda _path: importlib.import_module("trushell.commands.settings"),
)

# Provide a minimal module object with the expected function and
Expand All @@ -30,3 +34,29 @@ def run_settings():

out = capsys.readouterr().out
assert "Launch the TruShell settings TUI." in out


def test_run_help_lists_all_registry_commands(monkeypatch, capsys):
fake_kernel = SimpleNamespace(
registry={
"help": {"path": "trushell/commands/core.py", "function": "run_help"},
"task": {"path": "trushell/commands/tasks.py", "function": "run_task_command"},
"gstatus": {"path": "trushell/plugins/git_enhancer/main.py", "function": "plugin_init"},
},
_import_module=None,
)

monkeypatch.setattr("trushell.core.trukernel.get_kernel", lambda: fake_kernel)

run_help("")

out = capsys.readouterr().out
assert "gstatus" in out, "plugin-registered commands must appear in help output"
assert "task" in out
assert "help" in out


def test_handle_local_command_does_not_intercept_help():
"""help must reach the kernel's run_help(), not a hardcoded CLI handler."""
result = _handle_local_command("help", "")
assert result == "unhandled"
3 changes: 3 additions & 0 deletions tests/test_nav.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations

import sys
from pathlib import Path

import pytest
from trushell.commands.nav import run_jump


Expand All @@ -22,6 +24,7 @@ def test_run_jump_single_match(tmp_path, monkeypatch):
assert result == f"__TRUSHELL_CD__: {target}"


@pytest.mark.skipif(sys.platform == "win32", reason="Rich table renders Windows paths with escaped backslashes, breaking cross-platform path comparison")
def test_run_jump_multiple_matches(tmp_path, monkeypatch):
first = tmp_path / "src"
first.mkdir()
Expand Down
3 changes: 0 additions & 3 deletions trushell/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,6 @@ def _handle_local_command(command: str, argument: str) -> str:

launch_settings()
return "handled"
if command == "help":
typer.echo("Available commands: joke, joke_trex, addtask, deletetask, updatetask, completetask, showtasks, now, time, world, tz, alarm, sw, settings, exit, help")
return "handled"
return "unhandled"


Expand Down
Loading