tirith ran `tirith check` on every terminal command, default-on with fail-open, as a pinned PM binary auto-downloaded at startup. Measured against the shipped 0.4.2 binary it caught 14/16 attack commands but flagged 12/24 everyday agent commands (`echo $((6*7))`, `"$VAR/x.sh"`, `gh api | python3`, LAN curls, `.dev` hosts, `pip install` via analysis_incomplete), and in cron/-q deny mode each of those is a hard block. It only ever saw terminal strings (execute_code, writes and the browser bypass it), fail-open meant no protection wherever the binary could not install (Windows, Termux, x64 musl), and it produced a steady issue/PR stream (fail_open semantics four times, install loops filling disks, a SIGSEGV hang, session-approval scoping, third-party promo text on approval cards). Removed: tools/tirith_security.py, the tirith branches of the command guard (the is_tirith tuple, session-only "always" downgrade, import-error/fail-open paths, unattended tirith pass), the CLI and gateway startup installers, the PM package and lock pins, the nix runtime dep, security.tirith_* defaults, the CLI warning string and three tirith tips (gaps refilled from the tail so translations survive), plus the gateway's "no automated assessor" warning that only existed to point at tirith. Config v50 drops security.tirith_* and enables nothing in its place. Hermes ships no tirith plugin; if tirith upstream publishes one, it goes through normal catalog admission under their name.
85 lines
2.7 KiB
Python
85 lines
2.7 KiB
Python
"""Regression tests for scanner-safe GitHub credential extraction (#22722)."""
|
|
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
HELPER = REPO_ROOT / "skills/software-development/github/scripts/git-credential-token.py"
|
|
|
|
|
|
def _extract(path: Path) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
[sys.executable, str(HELPER), str(path)],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
|
|
def _credential_file(tmp_path: Path, value: str) -> Path:
|
|
credentials = tmp_path / "credentials"
|
|
credentials.write_text(value, encoding="utf-8", newline="")
|
|
return credentials
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("credential", "token"),
|
|
[
|
|
("https://octocat:password-form-token@github.com\n", "password-form-token"),
|
|
("https://oauth-token:x-oauth-basic@github.com\n", "oauth-token"),
|
|
("https://ghp_token_only@github.com\n", "ghp_token_only"),
|
|
("https://github_pat_token_only@github.com\n", "github_pat_token_only"),
|
|
],
|
|
)
|
|
def test_extracts_supported_git_credential_url_forms(tmp_path, credential, token):
|
|
result = _extract(_credential_file(tmp_path, credential))
|
|
|
|
assert result.returncode == 0
|
|
assert result.stdout == f"{token}\n"
|
|
assert result.stderr == ""
|
|
|
|
|
|
def test_extracts_password_from_exact_github_https_credential(tmp_path):
|
|
credentials = _credential_file(
|
|
tmp_path,
|
|
"https://ignored:wrong@example.com\n"
|
|
"https://octocat:secret%2Ftoken@github.com\n",
|
|
)
|
|
|
|
result = _extract(credentials)
|
|
|
|
assert result.returncode == 0
|
|
assert result.stdout == "secret/token\n"
|
|
assert result.stderr == ""
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"credential",
|
|
[
|
|
"https://octocat:stolen@github.com.attacker.example\n",
|
|
"https://octocat@github.com\n",
|
|
"https://%6fctocat@github.com\n",
|
|
"https://octocat:token@github.com%2eattacker.example\n",
|
|
"https://octocat:token%0D%0AX-Injected%3Ayes@github.com\n",
|
|
"https://ghp_token%0Ainjected@github.com\n",
|
|
"https://octocat:token%00suffix@github.com\n",
|
|
"https://octocat:token%09suffix@github.com\n",
|
|
"https://octocat:token%C2%85suffix@github.com\n",
|
|
"https://ghp_token%1Fsuffix@github.com\n",
|
|
"https://ghp_token%C2%9Fsuffix@github.com\n",
|
|
"https://octocat:bad%ZZtoken@github.com\n",
|
|
"https://octocat:token@github.com:bogus\n",
|
|
"http://octocat:token@github.com\n",
|
|
],
|
|
)
|
|
def test_rejects_ambiguous_lookalike_or_malformed_credentials(tmp_path, credential):
|
|
result = _extract(_credential_file(tmp_path, credential))
|
|
|
|
assert result.returncode == 1
|
|
assert result.stdout == ""
|
|
assert result.stderr == ""
|