mirror of
https://github.com/Strategic-Automation/violin.git
synced 2026-08-14 12:33:37 +02:00
Merge P0 release and CI hardening
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
name: guard-check
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches: [master, dev]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
branches: [master, dev]
|
||||
jobs:
|
||||
guard-check:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -12,13 +12,14 @@ jobs:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Install dependencies
|
||||
run: pip install pyyaml
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
- name: Install development dependencies
|
||||
run: uv sync --dev
|
||||
- name: Run guard release check
|
||||
run: python scripts/violin_guard.py check-release
|
||||
run: uv run python scripts/violin_guard.py check-release
|
||||
- name: Validate YAML
|
||||
run: |
|
||||
python -c "
|
||||
uv run python -c "
|
||||
import yaml
|
||||
for f in ['distribution.yaml', 'config.yaml', 'skills/pentest/templates/scope-template.yaml']:
|
||||
yaml.safe_load(open(f))
|
||||
@@ -34,4 +35,4 @@ jobs:
|
||||
for stale in PLAN.md skills/pentest/scripts/recon-scan.sh; do
|
||||
if [ -f "$stale" ]; then echo "Stale: $stale"; exit 1; fi
|
||||
done
|
||||
echo "No stale artifacts found"
|
||||
echo "No stale artifacts found"
|
||||
|
||||
+2
-2
@@ -126,10 +126,10 @@ which <tool> # native
|
||||
|
||||
## Tool Philosophy
|
||||
|
||||
Violin runs on **Hermes built-in toolsets** (see [`skills/pentest/SKILL.md §1`](./skills/pentest/SKILL.md#1-operating-model) for the capability inventory and [`README.md §Toolsets`](./README.md#enabled-toolsets) for the full matrix). The canonical command-gate logic lives in `scripts/guard/` and is callable two ways:
|
||||
Violin runs on **Hermes built-in toolsets** (see [`skills/pentest/SKILL.md §1`](./skills/pentest/SKILL.md#1-operating-model) for the capability inventory and [`README.md §Toolsets`](./README.md#enabled-toolsets) for the full matrix). The canonical command-gate logic lives in `plugins/violin_guard/core/` and is callable two ways:
|
||||
|
||||
- **CLI:** `python scripts/violin_guard.py check-command ...` (the path documented in SKILL.md §2).
|
||||
- **Hermes plugin:** `plugins/violin_guard/` registers typed guard tools (`violin_check_command`, `violin_record_*`, `violin_exec`, `violin_sync_done`, …) that delegate to the same `scripts/guard/` state machine. Loading the plugin is optional but recommended — it surfaces the guards as first-class tools.
|
||||
- **Hermes plugin:** `plugins/violin_guard/` registers typed guard tools (`violin_check_command`, `violin_record_*`, `violin_exec`, `violin_sync_done`, …) that call the core service directly. The plugin is required for target execution.
|
||||
|
||||
Both entry points enforce the identical skill-load, active-PTT, history-freshness, hypothesis, and doc-sync gates. The executor itself writes exact history, but never updates PTT progress; that remains an explicit reviewed checkpoint after each bounded batch. Do **not** develop a third implementation.
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## 1.3.1
|
||||
|
||||
- Enforced scope authorisation, exclusions, phase-aligned PTT tasks, and relevant hypotheses at the execution boundary.
|
||||
- Made synchronization credits apply to all target-touching commands and bound reviewed batches to their captured PTT task.
|
||||
- Serialized guard state transitions, fixed isolated plugin imports, and made release and PowerShell smoke checks fail reliably.
|
||||
|
||||
## 1.3.0
|
||||
|
||||
- Consolidated Violin Guard into a Hermes-native plugin.
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<b>31 playbooks · 8 references · 1 optional guard plugin · 0 brokers · Hermes-native</b>
|
||||
<b>31 playbooks · 8 references · required execution guard · 0 brokers · Hermes-native</b>
|
||||
</p>
|
||||
|
||||
Violin is a **Hermes-native agentic pentest profile** for supervised, authorised penetration tests — from reconnaissance through safe exploit validation to reporting. It uses Hermes' built-in toolsets, skill-based playbooks, an optional `violin-guard` plugin, and lightweight guard scripts. No extra API keys, no per-profile credentials, no lock-in.
|
||||
Violin is a **Hermes-native agentic pentest profile** for supervised, authorised penetration tests — from reconnaissance through safe exploit validation to reporting. It uses Hermes' built-in toolsets, skill-based playbooks, and the required `violin-guard` plugin at the target-execution boundary. The standalone CLI is retained only for diagnostics and recovery. No extra API keys, no per-profile credentials, no lock-in.
|
||||
|
||||
```
|
||||
hermes profile install https://github.com/Strategic-Automation/violin
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# violin - supervised agentic Hermes pentest profile
|
||||
name: violin
|
||||
version: 1.3.0
|
||||
version: 1.3.1
|
||||
description: "A supervised agentic Hermes penetration testing profile for authorised Kali/Parrot-based security assessment, reconnaissance, exploit validation, and reporting workflows."
|
||||
hermes_requires: ">=0.18.0"
|
||||
author: "Violin contributors"
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
"""Violin guard — core subpackage with the shared state machine, execution, and adapters.
|
||||
|
||||
All modules in this package define their public API in ``__all__``. Functions that
|
||||
depend on the ``scripts/guard/`` package use lazy imports inside their bodies so
|
||||
the module graph is resolvable without ``scripts/`` on ``sys.path`` at import time.
|
||||
All modules in this package define their public API in ``__all__`` and are
|
||||
importable directly from the installed plugin directory.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Check-command sub-guards — pure validation functions.
|
||||
|
||||
All logic ported from scripts/guard/{command,record,freshness,closeout}.py
|
||||
This is the canonical command, freshness, and closeout policy implementation.
|
||||
No subprocess calls — pure functions returning dataclasses.
|
||||
"""
|
||||
|
||||
|
||||
@@ -133,17 +133,30 @@ def check_release() -> ReleaseCheckResult:
|
||||
result.add_warning("CHANGELOG.md not found")
|
||||
|
||||
# 3. Isolated plugin import (catches broken module-level code / imports).
|
||||
module_name = "violin_guard_release_check"
|
||||
old_module = sys.modules.get(module_name)
|
||||
try:
|
||||
sys.path.insert(0, str(root.parents[1]))
|
||||
sys.path.insert(0, str(root.parent))
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"violin_guard_release_check", root / "__init__.py"
|
||||
module_name,
|
||||
root / "__init__.py",
|
||||
submodule_search_locations=[str(root)],
|
||||
)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError("could not build plugin import specification")
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = mod
|
||||
spec.loader.exec_module(mod)
|
||||
result.add_info("isolated plugin import OK")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
result.add_error(f"plugin import failed: {type(exc).__name__}: {exc}")
|
||||
mod = None
|
||||
finally:
|
||||
sys.path.pop(0)
|
||||
if old_module is None:
|
||||
sys.modules.pop(module_name, None)
|
||||
else:
|
||||
sys.modules[module_name] = old_module
|
||||
|
||||
# 3b. Manifest vs registered tools.
|
||||
if mod is not None:
|
||||
@@ -178,7 +191,16 @@ def check_release() -> ReleaseCheckResult:
|
||||
result.add_warning("ruff not installed; skipped")
|
||||
try:
|
||||
pytest = subprocess.run(
|
||||
[sys.executable, "-m", "pytest", "-q", "-p", "no:cacheprovider"],
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"pytest",
|
||||
"-q",
|
||||
"-p",
|
||||
"no:cacheprovider",
|
||||
"--basetemp",
|
||||
str(Path(repo_root) / "engagements" / ".pytest-release"),
|
||||
],
|
||||
cwd=repo_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
@@ -204,12 +226,11 @@ def check_release() -> ReleaseCheckResult:
|
||||
# 6. Skill documentation staleness scan (corrected forbidden set).
|
||||
profile_root = root.parents[1]
|
||||
skills_root = profile_root / "skills"
|
||||
# 'violin_record_history' is RE-REGISTERED in __init__.py, so it is no
|
||||
# longer a stale reference; 'violin_message_tick' is genuinely absent.
|
||||
forbidden = {
|
||||
"scripts/guard/": "removed legacy guard package",
|
||||
"hypothesis_guard.py": "removed hypothesis wrapper",
|
||||
"session_search": "unavailable session-search tool",
|
||||
"violin_record_history": "removed executor-owned history tool",
|
||||
"violin_message_tick": "removed model-visible message tool",
|
||||
"violin_guard.py close": "nonexistent close subcommand",
|
||||
"check-closeout": "nonexistent closeout subcommand",
|
||||
|
||||
@@ -8,6 +8,7 @@ import re
|
||||
from pathlib import Path
|
||||
|
||||
from . import command, execution, hypotheses, ptt, state
|
||||
from .adapters import search_exploit
|
||||
|
||||
|
||||
def _json(status_name, **payload):
|
||||
@@ -335,9 +336,4 @@ def handle_status(a, **kwargs):
|
||||
|
||||
|
||||
def handle_search_exploit(a, **kwargs):
|
||||
return _json(
|
||||
"ok",
|
||||
**__import__(
|
||||
"plugins.violin_guard.core.adapters", fromlist=["search_exploit"]
|
||||
).search_exploit(a),
|
||||
)
|
||||
return _json("ok", **search_exploit(a))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: violin-guard
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
description: Typed scope guards and an execute-and-record boundary with bounded synchronization windows.
|
||||
kind: standalone
|
||||
provides_tools:
|
||||
@@ -26,4 +26,4 @@ hooks:
|
||||
toolsets:
|
||||
violin_guard:
|
||||
description: Violin engagement guard tools
|
||||
app_style: sh
|
||||
app_style: sh
|
||||
|
||||
@@ -38,7 +38,7 @@ RECORD_PTT_SCHEMA = {
|
||||
}
|
||||
|
||||
RECORD_HYPOTHESIS_SCHEMA = {
|
||||
"description": "Record/update a hypothesis row (delegates to hypothesis_guard.py record-hypothesis).",
|
||||
"description": "Record or update a hypothesis row in the engagement state.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "violin"
|
||||
version = "1.2.0"
|
||||
version = "1.3.1"
|
||||
description = "Supervised agentic Hermes penetration-testing profile"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = []
|
||||
|
||||
@@ -10,6 +10,7 @@ Write-Host "Violin smoke test (PowerShell)"
|
||||
Write-Host "Repo: $RepoRoot"
|
||||
|
||||
python scripts/violin_guard.py check-release
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
if ($NoHermes) {
|
||||
Write-Host "Skipping Hermes install smoke because -NoHermes was supplied."
|
||||
@@ -25,9 +26,13 @@ if (-not $Hermes) {
|
||||
$ProfileName = "violin-smoke-$([int][double]::Parse((Get-Date -UFormat %s)))"
|
||||
try {
|
||||
hermes profile install . --name $ProfileName -y
|
||||
if ($LASTEXITCODE -ne 0) { throw "Hermes profile install failed." }
|
||||
hermes profile show $ProfileName
|
||||
if ($LASTEXITCODE -ne 0) { throw "Hermes profile show failed." }
|
||||
hermes -p $ProfileName tools --summary
|
||||
if ($LASTEXITCODE -ne 0) { throw "Hermes tool listing failed." }
|
||||
hermes -p $ProfileName chat -q "Smoke test: reply with Violin profile loaded" -Q
|
||||
if ($LASTEXITCODE -ne 0) { throw "Hermes chat smoke test failed." }
|
||||
}
|
||||
finally {
|
||||
hermes profile delete $ProfileName -y 2>$null
|
||||
|
||||
@@ -657,11 +657,11 @@ s4 = st(tools.handle_exec, **base2)
|
||||
assert s4 == "approved", f"step4 expected approved, got {s4}"
|
||||
print(" ok: violin_exec after sync -> approved")
|
||||
|
||||
# Step 5: hypothesis recording via plugin routes to hypothesis_guard.py
|
||||
# Step 5: hypothesis recording through the plugin core service.
|
||||
s5 = st(tools.handle_record_hypothesis, eng_dir=eng_dir, service="SMB",
|
||||
port="445", title="anon access", status="researching")
|
||||
assert s5 == "ok", f"step5 expected ok, got {s5}"
|
||||
print(" ok: violin_record_hypothesis -> ok (routed to hypothesis_guard.py)")
|
||||
print(" ok: violin_record_hypothesis -> ok")
|
||||
print("GATES_OK")
|
||||
PY
|
||||
gates_exit=$?
|
||||
|
||||
Reference in New Issue
Block a user