hermia 0.1.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.
- hermia-0.1.1/.github/ISSUE_TEMPLATE/bug_report.md +49 -0
- hermia-0.1.1/.github/ISSUE_TEMPLATE/feature_request.md +36 -0
- hermia-0.1.1/.github/dependabot.yml +13 -0
- hermia-0.1.1/.github/pull_request_template.md +19 -0
- hermia-0.1.1/.github/workflows/ci.yml +34 -0
- hermia-0.1.1/.github/workflows/publish.yml +55 -0
- hermia-0.1.1/.github/workflows/security.yml +171 -0
- hermia-0.1.1/.gitignore +38 -0
- hermia-0.1.1/.gitleaks.toml +100 -0
- hermia-0.1.1/.pre-commit-config.yaml +18 -0
- hermia-0.1.1/.secrets.baseline +171 -0
- hermia-0.1.1/AGENTS.md +214 -0
- hermia-0.1.1/CHANGELOG.md +63 -0
- hermia-0.1.1/CLAUDE.md +64 -0
- hermia-0.1.1/CODE_OF_CONDUCT.md +58 -0
- hermia-0.1.1/CONTRIBUTING.md +122 -0
- hermia-0.1.1/LICENSE +21 -0
- hermia-0.1.1/PKG-INFO +278 -0
- hermia-0.1.1/README.md +236 -0
- hermia-0.1.1/SECURITY.md +76 -0
- hermia-0.1.1/assets/demo.mp4 +0 -0
- hermia-0.1.1/assets/goldilocks.cast +2841 -0
- hermia-0.1.1/assets/goldilocks.mp4 +0 -0
- hermia-0.1.1/docs/demo-reference-2026-05-21.md +111 -0
- hermia-0.1.1/docs/llm-benchmark-landscape-2026.md +330 -0
- hermia-0.1.1/docs/roadmap.md +554 -0
- hermia-0.1.1/docs/security-framework-research.md +281 -0
- hermia-0.1.1/docs/specs/hermia-0ws-spec.md +232 -0
- hermia-0.1.1/docs/specs/hermia-gx8-spec.md +149 -0
- hermia-0.1.1/docs/specs/hermia-tun-spec.md +353 -0
- hermia-0.1.1/docs/specs/hermia-w59-spec.md +222 -0
- hermia-0.1.1/docs/usage.md +300 -0
- hermia-0.1.1/hermia-fleet.yaml +27 -0
- hermia-0.1.1/pyproject.toml +87 -0
- hermia-0.1.1/scripts/add_audit_columns.sql +3 -0
- hermia-0.1.1/scripts/add_findings_table.sql +48 -0
- hermia-0.1.1/scripts/add_framework_columns.sql +19 -0
- hermia-0.1.1/scripts/add_judge_columns.sql +8 -0
- hermia-0.1.1/scripts/add_mode_columns.sql +2 -0
- hermia-0.1.1/scripts/create_table.sql +37 -0
- hermia-0.1.1/scripts/hermia +10 -0
- hermia-0.1.1/src/hermia/__init__.py +3 -0
- hermia-0.1.1/src/hermia/analyze.py +484 -0
- hermia-0.1.1/src/hermia/app.py +107 -0
- hermia-0.1.1/src/hermia/audit.py +235 -0
- hermia-0.1.1/src/hermia/export.py +232 -0
- hermia-0.1.1/src/hermia/fleet.py +121 -0
- hermia-0.1.1/src/hermia/metrics.py +462 -0
- hermia-0.1.1/src/hermia/preflight.py +209 -0
- hermia-0.1.1/src/hermia/regression.py +270 -0
- hermia-0.1.1/src/hermia/results.py +72 -0
- hermia-0.1.1/src/hermia/robustness.py +123 -0
- hermia-0.1.1/src/hermia/runner.py +250 -0
- hermia-0.1.1/src/hermia/schemas.py +332 -0
- hermia-0.1.1/src/hermia/screens.py +418 -0
- hermia-0.1.1/test-datasets/agentic-tasks.json +455 -0
- hermia-0.1.1/tests/__init__.py +0 -0
- hermia-0.1.1/tests/integration/__init__.py +0 -0
- hermia-0.1.1/tests/integration/conftest.py +87 -0
- hermia-0.1.1/tests/integration/test_determinism.py +93 -0
- hermia-0.1.1/tests/integration/test_fake_ollama.py +110 -0
- hermia-0.1.1/tests/security/__init__.py +0 -0
- hermia-0.1.1/tests/security/test_adversarial_input_robustness.py +131 -0
- hermia-0.1.1/tests/security/test_prompt_injection.py +69 -0
- hermia-0.1.1/tests/security/test_robustness.py +187 -0
- hermia-0.1.1/tests/security/test_scope_escalation.py +51 -0
- hermia-0.1.1/tests/security/test_security_boundary.py +28 -0
- hermia-0.1.1/tests/security/test_structured_field_injection.py +103 -0
- hermia-0.1.1/tests/test_regression.py +454 -0
- hermia-0.1.1/tests/unit/__init__.py +0 -0
- hermia-0.1.1/tests/unit/test_analyze.py +434 -0
- hermia-0.1.1/tests/unit/test_app.py +160 -0
- hermia-0.1.1/tests/unit/test_audit.py +342 -0
- hermia-0.1.1/tests/unit/test_export.py +553 -0
- hermia-0.1.1/tests/unit/test_fleet.py +374 -0
- hermia-0.1.1/tests/unit/test_metrics.py +711 -0
- hermia-0.1.1/tests/unit/test_preflight.py +171 -0
- hermia-0.1.1/tests/unit/test_regression.py +366 -0
- hermia-0.1.1/tests/unit/test_results.py +147 -0
- hermia-0.1.1/tests/unit/test_robustness.py +191 -0
- hermia-0.1.1/tests/unit/test_runner.py +667 -0
- hermia-0.1.1/tests/unit/test_schemas.py +323 -0
- hermia-0.1.1/tests/unit/test_schemas_properties.py +362 -0
- hermia-0.1.1/tests/unit/test_screens.py +156 -0
- hermia-0.1.1/tests/unit/test_screens_aggregates.py +95 -0
- hermia-0.1.1/tests/unit/test_screens_pilot.py +556 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Something isn't working correctly
|
|
4
|
+
title: 'bug: '
|
|
5
|
+
labels: bug
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What happened
|
|
10
|
+
|
|
11
|
+
<!-- Clear description of the bug. -->
|
|
12
|
+
|
|
13
|
+
## Expected behavior
|
|
14
|
+
|
|
15
|
+
<!-- What should have happened instead. -->
|
|
16
|
+
|
|
17
|
+
## Steps to reproduce
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
1.
|
|
21
|
+
2.
|
|
22
|
+
3.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Environment
|
|
26
|
+
|
|
27
|
+
- OS:
|
|
28
|
+
- Python version:
|
|
29
|
+
- Ollama version:
|
|
30
|
+
- Model(s) under test:
|
|
31
|
+
- Hermia version / commit:
|
|
32
|
+
|
|
33
|
+
## Eval context (if applicable)
|
|
34
|
+
|
|
35
|
+
<!-- Which dimension or test ID is affected? Which model(s)? -->
|
|
36
|
+
|
|
37
|
+
- Test ID:
|
|
38
|
+
- Model:
|
|
39
|
+
- Dimension:
|
|
40
|
+
|
|
41
|
+
## Relevant output
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
paste terminal output here
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Additional context
|
|
48
|
+
|
|
49
|
+
<!-- Anything else that might be relevant. -->
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Propose a new eval test, dimension, or capability
|
|
4
|
+
title: 'feat: '
|
|
5
|
+
labels: enhancement
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What problem does this solve
|
|
10
|
+
|
|
11
|
+
<!-- Why does this need to exist? What gap does it fill? -->
|
|
12
|
+
|
|
13
|
+
## Proposed solution
|
|
14
|
+
|
|
15
|
+
<!-- What would you build? Keep it specific. -->
|
|
16
|
+
|
|
17
|
+
## Framework mapping (for new eval tests)
|
|
18
|
+
|
|
19
|
+
<!-- If proposing a new eval test or dimension, which framework(s) does it map to?
|
|
20
|
+
OWASP LLM Top 10, MITRE ATLAS, CSA MAESTRO, NIST AI RMF — or a gap none of them cover. -->
|
|
21
|
+
|
|
22
|
+
- Framework:
|
|
23
|
+
- Reference (e.g. OWASP LLM01, ATLAS AML.T0054):
|
|
24
|
+
|
|
25
|
+
## Alternatives considered
|
|
26
|
+
|
|
27
|
+
<!-- What else could solve this problem? Why is your proposal better? -->
|
|
28
|
+
|
|
29
|
+
## Scope estimate
|
|
30
|
+
|
|
31
|
+
<!-- How much of the codebase does this touch?
|
|
32
|
+
New eval test only / schema changes / runner changes / TUI changes / CI changes -->
|
|
33
|
+
|
|
34
|
+
## Additional context
|
|
35
|
+
|
|
36
|
+
<!-- Links, prior art, related issues. -->
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
## What
|
|
2
|
+
<!-- One sentence: what does this change do? -->
|
|
3
|
+
|
|
4
|
+
## Why
|
|
5
|
+
<!-- Why is this change needed? Link to issue or framework gap if applicable (e.g., OWASP LLM01, ATLAS AML.T0054). -->
|
|
6
|
+
|
|
7
|
+
## Test coverage
|
|
8
|
+
<!-- What tests cover this? New tests added? Existing tests affected? -->
|
|
9
|
+
- [ ] New eval test cases added to `agentic-tasks.json`
|
|
10
|
+
- [ ] Unit tests added/updated in `tests/`
|
|
11
|
+
- [ ] Manually verified against local Ollama endpoint
|
|
12
|
+
|
|
13
|
+
## Security checklist
|
|
14
|
+
- [ ] No secrets, credentials, or API keys in diff
|
|
15
|
+
- [ ] No new dependencies without justification
|
|
16
|
+
- [ ] Bandit/pip-audit clean (CI will verify)
|
|
17
|
+
|
|
18
|
+
## Notes for reviewer
|
|
19
|
+
<!-- Anything Gemini Code Assist or a human reviewer should pay particular attention to. -->
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- "feature/**"
|
|
7
|
+
- "fix/**"
|
|
8
|
+
- "chore/**"
|
|
9
|
+
- "dev"
|
|
10
|
+
pull_request:
|
|
11
|
+
branches:
|
|
12
|
+
- dev
|
|
13
|
+
- main
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
lint-and-test:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
20
|
+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.11"
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: pip install -e ".[dev]"
|
|
26
|
+
|
|
27
|
+
- name: Lint (ruff)
|
|
28
|
+
run: ruff check src/ tests/
|
|
29
|
+
|
|
30
|
+
- name: Type check (mypy)
|
|
31
|
+
run: mypy src/
|
|
32
|
+
|
|
33
|
+
- name: Test (pytest)
|
|
34
|
+
run: pytest tests/
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
16
|
+
|
|
17
|
+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.11"
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: pip install -e ".[dev]"
|
|
23
|
+
|
|
24
|
+
- name: Lint (ruff)
|
|
25
|
+
run: ruff check src/ tests/
|
|
26
|
+
|
|
27
|
+
- name: Type check (mypy)
|
|
28
|
+
run: mypy src/
|
|
29
|
+
|
|
30
|
+
- name: Test (pytest)
|
|
31
|
+
run: pytest tests/
|
|
32
|
+
|
|
33
|
+
build-and-publish:
|
|
34
|
+
needs: test
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
environment: pypi
|
|
37
|
+
permissions:
|
|
38
|
+
contents: read
|
|
39
|
+
id-token: write
|
|
40
|
+
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
43
|
+
|
|
44
|
+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
45
|
+
with:
|
|
46
|
+
python-version: "3.11"
|
|
47
|
+
|
|
48
|
+
- name: Install build
|
|
49
|
+
run: pip install build==1.5.0
|
|
50
|
+
|
|
51
|
+
- name: Build
|
|
52
|
+
run: python -m build
|
|
53
|
+
|
|
54
|
+
- name: Publish to PyPI
|
|
55
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
name: Security
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main # full security gate before anything hits main
|
|
7
|
+
- dev # catch issues before promotion, not after
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: "0 6 * * 1" # weekly Monday 06:00 UTC — scans main regardless of activity
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
gitleaks:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
pull-requests: read
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
- uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2
|
|
22
|
+
env:
|
|
23
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
24
|
+
GITLEAKS_CONFIG: .gitleaks.toml
|
|
25
|
+
|
|
26
|
+
trufflehog:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
permissions:
|
|
29
|
+
contents: read
|
|
30
|
+
pull-requests: read
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
33
|
+
with:
|
|
34
|
+
fetch-depth: 0
|
|
35
|
+
- name: TruffleHog — entropy + credential scan
|
|
36
|
+
uses: trufflesecurity/trufflehog@0fa069c12f0c7baf431041cd1e564a9c5058846c # main 2026-05-16
|
|
37
|
+
|
|
38
|
+
trivy:
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
42
|
+
- name: Trivy dependency scan
|
|
43
|
+
uses: aquasecurity/trivy-action@314ff8b43182423b84c50b1670b0e10f858f2d98 # master 2026-05-16
|
|
44
|
+
with:
|
|
45
|
+
scan-type: fs
|
|
46
|
+
scan-ref: "."
|
|
47
|
+
exit-code: "1"
|
|
48
|
+
severity: HIGH,CRITICAL
|
|
49
|
+
|
|
50
|
+
bandit:
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
54
|
+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
55
|
+
with:
|
|
56
|
+
python-version: "3.11"
|
|
57
|
+
- name: Install bandit
|
|
58
|
+
run: pip install bandit
|
|
59
|
+
- name: Bandit SAST
|
|
60
|
+
run: bandit -r src/ -ll
|
|
61
|
+
|
|
62
|
+
pip-audit:
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
66
|
+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
67
|
+
with:
|
|
68
|
+
python-version: "3.11"
|
|
69
|
+
- name: Audit hermia dependencies in isolated venv
|
|
70
|
+
run: |
|
|
71
|
+
python -m venv /tmp/audit-env
|
|
72
|
+
/tmp/audit-env/bin/pip install --quiet --upgrade pip
|
|
73
|
+
/tmp/audit-env/bin/pip install --quiet pip-audit
|
|
74
|
+
/tmp/audit-env/bin/pip install --quiet -e .
|
|
75
|
+
# CVE-2026-3219 affects pip itself (no fix available yet); all hermia deps are clean
|
|
76
|
+
/tmp/audit-env/bin/pip-audit --skip-editable --ignore-vuln CVE-2026-3219
|
|
77
|
+
|
|
78
|
+
guarddog-changes:
|
|
79
|
+
runs-on: ubuntu-latest
|
|
80
|
+
outputs:
|
|
81
|
+
relevant: ${{ steps.filter.outputs.relevant }}
|
|
82
|
+
steps:
|
|
83
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
84
|
+
with:
|
|
85
|
+
fetch-depth: 0
|
|
86
|
+
- id: filter
|
|
87
|
+
run: |
|
|
88
|
+
if [[ "${{ github.event_name }}" == "schedule" ]]; then
|
|
89
|
+
echo "relevant=true" >> "$GITHUB_OUTPUT"
|
|
90
|
+
elif git diff --name-only "origin/${{ github.base_ref }}...HEAD" \
|
|
91
|
+
| grep -qE '^(pyproject\.toml|\.github/workflows/security\.yml)$'; then
|
|
92
|
+
echo "relevant=true" >> "$GITHUB_OUTPUT"
|
|
93
|
+
else
|
|
94
|
+
echo "relevant=false" >> "$GITHUB_OUTPUT"
|
|
95
|
+
echo "No dep or workflow changes — skipping guarddog"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
guarddog:
|
|
99
|
+
needs: guarddog-changes
|
|
100
|
+
if: needs.guarddog-changes.outputs.relevant == 'true'
|
|
101
|
+
runs-on: ubuntu-latest
|
|
102
|
+
timeout-minutes: 10
|
|
103
|
+
steps:
|
|
104
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
105
|
+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
106
|
+
with:
|
|
107
|
+
python-version: "3.11"
|
|
108
|
+
- name: Install guarddog
|
|
109
|
+
# pinned to avoid silent rule/CLI changes; bump quarterly and re-validate rule names
|
|
110
|
+
run: pip install "guarddog==2.10.0"
|
|
111
|
+
- name: GuardDog — typosquatting (hard fail)
|
|
112
|
+
run: |
|
|
113
|
+
python3 - <<'EOF'
|
|
114
|
+
import tomllib, subprocess, sys
|
|
115
|
+
from packaging.requirements import Requirement
|
|
116
|
+
from packaging.utils import canonicalize_name
|
|
117
|
+
with open("pyproject.toml", "rb") as f:
|
|
118
|
+
data = tomllib.load(f)
|
|
119
|
+
project = data.get("project", {})
|
|
120
|
+
all_deps = list(project.get("dependencies", []))
|
|
121
|
+
for group in project.get("optional-dependencies", {}).values():
|
|
122
|
+
all_deps += group
|
|
123
|
+
pkgs = sorted({canonicalize_name(Requirement(d).name) for d in all_deps})
|
|
124
|
+
failed = False
|
|
125
|
+
for pkg in pkgs:
|
|
126
|
+
print(f"Scanning {pkg}...")
|
|
127
|
+
r = subprocess.run(
|
|
128
|
+
["guarddog", "pypi", "scan", pkg,
|
|
129
|
+
"--exit-non-zero-on-finding",
|
|
130
|
+
"--rules", "typosquatting"],
|
|
131
|
+
check=False,
|
|
132
|
+
)
|
|
133
|
+
if r.returncode != 0:
|
|
134
|
+
failed = True
|
|
135
|
+
if failed:
|
|
136
|
+
sys.exit(1)
|
|
137
|
+
EOF
|
|
138
|
+
- name: GuardDog — heuristic rules (warn only)
|
|
139
|
+
continue-on-error: true
|
|
140
|
+
run: |
|
|
141
|
+
python3 - <<'EOF'
|
|
142
|
+
import tomllib, subprocess, os
|
|
143
|
+
from packaging.requirements import Requirement
|
|
144
|
+
from packaging.utils import canonicalize_name
|
|
145
|
+
with open("pyproject.toml", "rb") as f:
|
|
146
|
+
data = tomllib.load(f)
|
|
147
|
+
project = data.get("project", {})
|
|
148
|
+
all_deps = list(project.get("dependencies", []))
|
|
149
|
+
for group in project.get("optional-dependencies", {}).values():
|
|
150
|
+
all_deps += group
|
|
151
|
+
pkgs = sorted({canonicalize_name(Requirement(d).name) for d in all_deps})
|
|
152
|
+
findings = []
|
|
153
|
+
for pkg in pkgs:
|
|
154
|
+
print(f"Scanning {pkg}...")
|
|
155
|
+
r = subprocess.run(
|
|
156
|
+
["guarddog", "pypi", "scan", pkg,
|
|
157
|
+
"--exit-non-zero-on-finding",
|
|
158
|
+
"--rules", "potentially_compromised_email_domain",
|
|
159
|
+
"--rules", "obfuscation"],
|
|
160
|
+
check=False, capture_output=True, text=True,
|
|
161
|
+
)
|
|
162
|
+
print(r.stdout)
|
|
163
|
+
if r.returncode != 0:
|
|
164
|
+
findings.append((pkg, r.stdout))
|
|
165
|
+
summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
|
|
166
|
+
if summary_path and findings:
|
|
167
|
+
with open(summary_path, "a") as f:
|
|
168
|
+
f.write("## :warning: GuardDog heuristic findings (warn only)\n\n")
|
|
169
|
+
for pkg, output in findings:
|
|
170
|
+
f.write(f"### `{pkg}`\n```\n{output.strip()}\n```\n\n")
|
|
171
|
+
EOF
|
hermia-0.1.1/.gitignore
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
venv/
|
|
3
|
+
__pycache__/
|
|
4
|
+
._*
|
|
5
|
+
.coverage
|
|
6
|
+
uv.lock
|
|
7
|
+
*.pyc
|
|
8
|
+
*.pyo
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
dist/
|
|
13
|
+
*.egg-info/
|
|
14
|
+
results/
|
|
15
|
+
.env
|
|
16
|
+
.env.*
|
|
17
|
+
!.env.template
|
|
18
|
+
session-notes/
|
|
19
|
+
prompt.txt
|
|
20
|
+
docs/talk-*.md
|
|
21
|
+
docs/talk-*.js
|
|
22
|
+
docs/*.pptx
|
|
23
|
+
docs/*.pdf
|
|
24
|
+
docs/slide-*.jpg
|
|
25
|
+
docs/~$*
|
|
26
|
+
logs/
|
|
27
|
+
analysis/*.jsonl
|
|
28
|
+
scripts/collect_and_push.sh
|
|
29
|
+
scripts/run_analyze.sh
|
|
30
|
+
scripts/seed_findings.py
|
|
31
|
+
.claude/
|
|
32
|
+
analysis/*.md
|
|
33
|
+
analysis/*.html
|
|
34
|
+
docs/*.docx
|
|
35
|
+
|
|
36
|
+
# Secret scanning
|
|
37
|
+
# .secrets.baseline is intentionally tracked (audited false-positive list)
|
|
38
|
+
# .gitleaks.toml is intentionally tracked (custom rules)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Hermia — Gitleaks configuration
|
|
2
|
+
# Extends the default ruleset with custom rules for internal infrastructure strings.
|
|
3
|
+
# Run: gitleaks detect --config .gitleaks.toml
|
|
4
|
+
# Docs: https://github.com/zricethezav/gitleaks/tree/master#configuration
|
|
5
|
+
|
|
6
|
+
title = "hermia-gitleaks"
|
|
7
|
+
|
|
8
|
+
[extend]
|
|
9
|
+
# Inherit the full upstream default ruleset, then layer our rules on top.
|
|
10
|
+
useDefault = true
|
|
11
|
+
|
|
12
|
+
# ── Custom rules ─────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
[[rules]]
|
|
15
|
+
id = "tailscale-ip"
|
|
16
|
+
description = "Tailscale CGNAT IP (100.64.0.0/10) — covers all fleet nodes past and future"
|
|
17
|
+
regex = '''(?i)(tailscale|ts[_\-]?ip|node[_\-]?ip|host)[^\n]{0,40}100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\.\d{1,3}\.\d{1,3}\b'''
|
|
18
|
+
tags = ["tailscale", "internal-ip"]
|
|
19
|
+
|
|
20
|
+
[[rules]]
|
|
21
|
+
id = "tailscale-ip-bare"
|
|
22
|
+
description = "Bare Tailscale CGNAT IP with no keyword context — belt-and-suspenders"
|
|
23
|
+
regex = '''\b100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\.\d{1,3}\.\d{1,3}\b'''
|
|
24
|
+
tags = ["tailscale", "internal-ip"]
|
|
25
|
+
|
|
26
|
+
[[rules]]
|
|
27
|
+
id = "tailscale-hostname"
|
|
28
|
+
description = "Any *.tailXXXX.ts.net hostname — catches all tailnets including future ones"
|
|
29
|
+
regex = '''\b[a-z0-9][a-z0-9\-]*\.tail[a-z0-9]+\.ts\.net\b'''
|
|
30
|
+
tags = ["tailscale", "internal-hostname"]
|
|
31
|
+
|
|
32
|
+
[[rules]]
|
|
33
|
+
id = "rfc1918-172-16"
|
|
34
|
+
description = "RFC 1918 172.16–31.x.x (Docker bridge, internal subnets)"
|
|
35
|
+
regex = '''\b172\.(1[6-9]|2[0-9]|3[01])\.\d{1,3}\.\d{1,3}\b'''
|
|
36
|
+
tags = ["internal-ip", "docker"]
|
|
37
|
+
|
|
38
|
+
[[rules]]
|
|
39
|
+
id = "internal-ssh-alias"
|
|
40
|
+
description = "Original internal SSH host aliases — catch if accidentally re-introduced"
|
|
41
|
+
regex = '''\b(m3pro-work|m3pro|m1pro-personal|m1pro|openclaw-linux|openclaw)\b'''
|
|
42
|
+
tags = ["internal-hostname", "ssh"]
|
|
43
|
+
|
|
44
|
+
[[rules]]
|
|
45
|
+
id = "internal-path-home-scott"
|
|
46
|
+
description = "/home/scott/* paths — personal home directory leakage"
|
|
47
|
+
regex = '''/home/scott/[^\s"'`]+'''
|
|
48
|
+
tags = ["internal-path"]
|
|
49
|
+
|
|
50
|
+
[[rules]]
|
|
51
|
+
id = "grafana-dashboard-uid"
|
|
52
|
+
description = "Grafana dashboard UID — full UUID or short UID (default 9-char) in URL or config"
|
|
53
|
+
# Grafana native UIDs are typically 9-char alphanumeric (/d/<uid>/); full UUIDs also used
|
|
54
|
+
regex = '''grafana[^\n]{0,80}(/d/[A-Za-z0-9_-]{8,40}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'''
|
|
55
|
+
tags = ["grafana", "internal-id"]
|
|
56
|
+
|
|
57
|
+
[[rules]]
|
|
58
|
+
id = "litellm-dispatch-key"
|
|
59
|
+
description = "LiteLLM dispatch/master key"
|
|
60
|
+
# Bridge pattern covers: env var assignments, Python dict literals, YAML config values
|
|
61
|
+
regex = '''(?i)(litellm[_\-]?(dispatch|master|api)[_\-]?key|LITELLM_[A-Z_]*KEY)[^\n]{0,40}["']?[A-Za-z0-9\-_]{20,}'''
|
|
62
|
+
tags = ["api-key", "litellm"]
|
|
63
|
+
|
|
64
|
+
[[rules]]
|
|
65
|
+
id = "postgres-dsn"
|
|
66
|
+
description = "PostgreSQL DSN with embedded credentials"
|
|
67
|
+
# Known gap: passwords containing raw '@' are missed; %40 (URL-encoded) is matched correctly
|
|
68
|
+
regex = '''postgresql://[^:/?#\s]+:(?:[^@\s"'`\\]|%40){6,}@\S+'''
|
|
69
|
+
tags = ["database", "credentials"]
|
|
70
|
+
|
|
71
|
+
# ── Allowlist ─────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
[allowlist]
|
|
74
|
+
description = "Safe strings that match rules but are intentionally present"
|
|
75
|
+
regexes = [
|
|
76
|
+
# RFC 5737 documentation addresses — used as replacements after purge
|
|
77
|
+
'''192\.0\.2\.\d+''',
|
|
78
|
+
# RFC 2606 documentation domains
|
|
79
|
+
'''hermia\.example\.com''',
|
|
80
|
+
# Placeholder tokens in docs/tests
|
|
81
|
+
'''YOUR-DASHBOARD-ID''',
|
|
82
|
+
'''YOUR-TAILSCALE-IP''',
|
|
83
|
+
'''YOUR-TAILNET''',
|
|
84
|
+
# Literal regex patterns in this config file itself
|
|
85
|
+
'''100\\\.\(6\[4-9\]''',
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
paths = [
|
|
89
|
+
# Test fixtures that intentionally contain RFC-reserved example addresses
|
|
90
|
+
"tests/",
|
|
91
|
+
# Lock file — no secrets, just hashes
|
|
92
|
+
"uv.lock",
|
|
93
|
+
# detect-secrets baseline records false-positives as JSON — not real secrets
|
|
94
|
+
".secrets.baseline",
|
|
95
|
+
# Fleet configs — intentionally contain fleet node hostnames and example labels
|
|
96
|
+
"hermia-fleet.yaml",
|
|
97
|
+
"fleet-.*\\.yaml",
|
|
98
|
+
"marcus-only.yaml",
|
|
99
|
+
"overnight.sh",
|
|
100
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/gitleaks/gitleaks
|
|
3
|
+
rev: c2afd56bbe7ab11564107d34efac4bfde3e3c78d # v8.24.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: gitleaks
|
|
6
|
+
args: ["--config", ".gitleaks.toml"]
|
|
7
|
+
|
|
8
|
+
- repo: https://github.com/Yelp/detect-secrets
|
|
9
|
+
rev: 01886c8a910c64595c47f186ca1ffc0b77fa5458 # v1.5.0
|
|
10
|
+
hooks:
|
|
11
|
+
- id: detect-secrets
|
|
12
|
+
args: ["--baseline", ".secrets.baseline"]
|
|
13
|
+
exclude: uv\.lock
|
|
14
|
+
|
|
15
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
16
|
+
rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # v5.0.0
|
|
17
|
+
hooks:
|
|
18
|
+
- id: detect-private-key
|