hermes-github-app-plugin 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/PKG-INFO +3 -3
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/README.md +2 -2
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/plugin.yaml +1 -1
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/pyproject.toml +1 -1
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/src/hermes_github_app_plugin/auth.py +3 -3
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/src/hermes_github_app_plugin/config.py +4 -1
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/tests/test_auth.py +3 -3
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/tests/test_cli.py +3 -3
- hermes_github_app_plugin-0.3.1/tests/test_install_scan.py +36 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/.github/dependabot.yml +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/.github/workflows/cd.yaml +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/.github/workflows/ci.yaml +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/.gitignore +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/LICENSE +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/skills/github-app-workflow/SKILL.md +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/src/hermes_github_app_plugin/__init__.py +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/src/hermes_github_app_plugin/cli.py +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/src/hermes_github_app_plugin/py.typed +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/src/hermes_github_app_plugin/schemas.py +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/src/hermes_github_app_plugin/skills/github-app-workflow/SKILL.md +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/src/hermes_github_app_plugin/tools.py +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/tests/conftest.py +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/tests/test_config.py +0 -0
- {hermes_github_app_plugin-0.3.0 → hermes_github_app_plugin-0.3.1}/tests/test_plugin.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: hermes-github-app-plugin
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Hermes plugin for per-agent GitHub App identity, gh/git wrappers, and GitHub App-aware tools.
|
|
5
5
|
Author: Hermes GitHub App Plugin Contributors
|
|
6
6
|
License: MIT
|
|
@@ -165,8 +165,8 @@ Before the first release, configure PyPI Trusted Publishing for this repository
|
|
|
165
165
|
Release example:
|
|
166
166
|
|
|
167
167
|
```bash
|
|
168
|
-
git tag 0.3.
|
|
169
|
-
git push origin 0.3.
|
|
168
|
+
git tag 0.3.1
|
|
169
|
+
git push origin 0.3.1
|
|
170
170
|
```
|
|
171
171
|
|
|
172
172
|
Tags like `v0.1.0`, `0.1`, or `0.1.0rc1` will not publish.
|
|
@@ -146,8 +146,8 @@ Before the first release, configure PyPI Trusted Publishing for this repository
|
|
|
146
146
|
Release example:
|
|
147
147
|
|
|
148
148
|
```bash
|
|
149
|
-
git tag 0.3.
|
|
150
|
-
git push origin 0.3.
|
|
149
|
+
git tag 0.3.1
|
|
150
|
+
git push origin 0.3.1
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
Tags like `v0.1.0`, `0.1`, or `0.1.0rc1` will not publish.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hermes-github-app-plugin"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Hermes plugin for per-agent GitHub App identity, gh/git wrappers, and GitHub App-aware tools."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -12,7 +12,7 @@ from urllib.parse import urlparse
|
|
|
12
12
|
import httpx
|
|
13
13
|
import jwt
|
|
14
14
|
|
|
15
|
-
from .config import
|
|
15
|
+
from .config import NONO_PLACEHOLDER, GitHubAppConfig
|
|
16
16
|
|
|
17
17
|
_MIN_REDACT_LENGTH = 8
|
|
18
18
|
_BROKERED_TOKEN_LIFETIME = timedelta(hours=1)
|
|
@@ -56,7 +56,7 @@ class GitHubAppAuth:
|
|
|
56
56
|
broker replaces it outside the sandbox.
|
|
57
57
|
"""
|
|
58
58
|
if self._config.token_broker:
|
|
59
|
-
return
|
|
59
|
+
return NONO_PLACEHOLDER
|
|
60
60
|
now = int(time.time())
|
|
61
61
|
payload = {"iat": now - 60, "exp": now + 9 * 60, "iss": self._config.client_id}
|
|
62
62
|
encoded = jwt.encode(payload, self._config.private_key, algorithm="RS256")
|
|
@@ -85,7 +85,7 @@ class GitHubAppAuth:
|
|
|
85
85
|
def _brokered_token(self, installation_id: str) -> InstallationToken:
|
|
86
86
|
"""Return the placeholder token with no network call; the broker injects the real one."""
|
|
87
87
|
return InstallationToken(
|
|
88
|
-
token=
|
|
88
|
+
token=NONO_PLACEHOLDER,
|
|
89
89
|
expires_at=datetime.now(timezone.utc) + _BROKERED_TOKEN_LIFETIME,
|
|
90
90
|
installation_id=installation_id,
|
|
91
91
|
client_id=self._config.client_id,
|
|
@@ -14,7 +14,10 @@ import yaml
|
|
|
14
14
|
# Token brokers keep the private key outside the agent's sandbox. The broker injects real
|
|
15
15
|
# credentials into outbound requests, so the agent only ever sends this placeholder.
|
|
16
16
|
SUPPORTED_TOKEN_BROKERS: tuple[str, ...] = ("nono",)
|
|
17
|
-
|
|
17
|
+
# Named so Hermes's plugin scanner doesn't read `<...>TOKEN = "<20+ chars>"` as a hardcoded secret
|
|
18
|
+
# and refuse the install; the old name stays as an alias for imports.
|
|
19
|
+
NONO_PLACEHOLDER = "nono-phantom-github-app"
|
|
20
|
+
NONO_PHANTOM_TOKEN = NONO_PLACEHOLDER
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
class ConfigurationError(RuntimeError):
|
|
@@ -15,7 +15,7 @@ from hermes_github_app_plugin.auth import (
|
|
|
15
15
|
repo_from_path,
|
|
16
16
|
requires_app_jwt,
|
|
17
17
|
)
|
|
18
|
-
from hermes_github_app_plugin.config import
|
|
18
|
+
from hermes_github_app_plugin.config import NONO_PLACEHOLDER, GitHubAppConfig
|
|
19
19
|
|
|
20
20
|
PRIVATE_KEY = "[REDACTED PRIVATE KEY]\n"
|
|
21
21
|
|
|
@@ -167,7 +167,7 @@ def test_broker_installation_token_makes_no_network_call() -> None:
|
|
|
167
167
|
|
|
168
168
|
token = auth.get_installation_token(repo="ExampleInfra/automation-config")
|
|
169
169
|
|
|
170
|
-
assert token.token ==
|
|
170
|
+
assert token.token == NONO_PLACEHOLDER
|
|
171
171
|
assert token.installation_id == "789"
|
|
172
172
|
assert token.app_slug == "hermes-test-agent"
|
|
173
173
|
assert token.client_id == "123"
|
|
@@ -186,7 +186,7 @@ def test_broker_create_jwt_returns_placeholder_without_signing(
|
|
|
186
186
|
monkeypatch.setattr("jwt.encode", fail_if_signing)
|
|
187
187
|
client = httpx.Client(transport=httpx.MockTransport(_fail_on_network))
|
|
188
188
|
|
|
189
|
-
assert GitHubAppAuth(_broker_config(), client=client).create_jwt() ==
|
|
189
|
+
assert GitHubAppAuth(_broker_config(), client=client).create_jwt() == NONO_PLACEHOLDER
|
|
190
190
|
|
|
191
191
|
|
|
192
192
|
def test_broker_request_sends_placeholder_and_reports_broker() -> None:
|
|
@@ -11,7 +11,7 @@ import yaml
|
|
|
11
11
|
|
|
12
12
|
from hermes_github_app_plugin import cli
|
|
13
13
|
from hermes_github_app_plugin.auth import GitHubAppAuth
|
|
14
|
-
from hermes_github_app_plugin.config import
|
|
14
|
+
from hermes_github_app_plugin.config import NONO_PLACEHOLDER, GitHubAppConfig
|
|
15
15
|
|
|
16
16
|
PRIVATE_KEY = "[REDACTED PRIVATE KEY]\n"
|
|
17
17
|
|
|
@@ -343,7 +343,7 @@ def test_doctor_token_broker_network_checks_send_placeholder(
|
|
|
343
343
|
|
|
344
344
|
assert cli._doctor("OWNER/REPO", skip_network=False) == 0
|
|
345
345
|
|
|
346
|
-
assert seen_auth == [f"Bearer {
|
|
346
|
+
assert seen_auth == [f"Bearer {NONO_PLACEHOLDER}"] * 2
|
|
347
347
|
output = capsys.readouterr().out
|
|
348
348
|
assert "✓ installation token brokered by nono" in output
|
|
349
349
|
assert "✓ /app API reachable: hermes-test-agent" in output
|
|
@@ -359,5 +359,5 @@ def test_token_command_in_broker_mode_prints_placeholder(
|
|
|
359
359
|
assert cli.main(args) == 0
|
|
360
360
|
|
|
361
361
|
captured = capsys.readouterr()
|
|
362
|
-
assert captured.out == f"{
|
|
362
|
+
assert captured.out == f"{NONO_PLACEHOLDER}\n"
|
|
363
363
|
assert "the nono proxy injects it" in captured.err
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Hermes refuses to install a plugin whose source trips its hardcoded-secret rule."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
# Hermes tools/skills_guard.py "hardcoded_secret": a critical finding that
|
|
9
|
+
# `hermes plugins install --force` cannot override.
|
|
10
|
+
HARDCODED_SECRET = re.compile(
|
|
11
|
+
r'(?:api[_-]?key|token|secret|password)\s*[=:]\s*["\']'
|
|
12
|
+
r'(?!(?-i:[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+)["\'])'
|
|
13
|
+
r"[A-Za-z0-9+/=_-]{20,}",
|
|
14
|
+
re.IGNORECASE,
|
|
15
|
+
)
|
|
16
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
17
|
+
SCANNED_SUFFIXES = {".py", ".yaml", ".yml", ".md", ".toml", ".json", ".sh", ".txt"}
|
|
18
|
+
SKIPPED_DIRS = {".git", "tests", ".venv"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _shipped_files() -> list[Path]:
|
|
22
|
+
return [
|
|
23
|
+
path
|
|
24
|
+
for path in ROOT.rglob("*")
|
|
25
|
+
if path.is_file() and path.suffix in SCANNED_SUFFIXES and not SKIPPED_DIRS & set(path.parts)
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_shipped_files_pass_the_hermes_hardcoded_secret_rule() -> None:
|
|
30
|
+
hits = []
|
|
31
|
+
for path in _shipped_files():
|
|
32
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
33
|
+
for number, line in enumerate(text.splitlines(), 1):
|
|
34
|
+
if HARDCODED_SECRET.search(line):
|
|
35
|
+
hits.append(f"{path.relative_to(ROOT)}:{number}")
|
|
36
|
+
assert hits == []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|