Make the product buyable and the open-source claim true. Licence system, end to end. license.rs was well-designed dead code; wire it up: an Ed25519-signed token (same key and verify-before-parse discipline as definition packs), `hound license install`, houndd loads and verifies at boot, and the execution gate and full supply-chain feed now gate on Capability checks. Verification failing always degrades to Free, never to a locked-out security tool; an expired licence downgrades with the reason shown. Adds tools/issue-license.py. Hound Linux threat pack. 34 curated YARA rules — miners, IoT/DDoS bots, backdoors, rootkits, ransomware, webshells, droppers, reverse shells — shipped through a new signed rules-pack channel (.rpack) alongside the definitions feed. Every rule is ELF- or size-anchored and keyed on family strings, never syscalls; the builder refuses to sign a pack that matches a system binary (the goodware gate caught two bad rules), and a regression test proves every rule fires on a sample and stays quiet on a document about malware. Action signature verification. The composite action claimed Ed25519 verification "against the same signed manifest the desktop agent uses" but only compared a same-host sha256. It now fetches latest.json, verifies the Ed25519 signature over the canonical release statement against the pinned release key, and installs the checksum from the verified manifest. Licence resolved to Apache-2.0: Cargo.toml, a real LICENSE file, README. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
71 lines
2.8 KiB
Python
Executable file
71 lines
2.8 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Issue a Hound licence token.
|
|
|
|
The token is an Ed25519 signature over the canonical licence text, wrapped
|
|
in the same signed-blob JSON as a definitions pack, then base64-encoded to
|
|
a single line a customer can paste into:
|
|
|
|
sudo hound license install <token>
|
|
|
|
Signed with the same key as definition packs and release manifests, and
|
|
verified by the agent against the same compiled-in public key. There is no
|
|
licence server and no phone-home: the token is the entire entitlement.
|
|
|
|
Usage:
|
|
tools/issue-license.py --tier pro --holder "Ada L <ada@example.com>" \
|
|
--expires 2027-08-21
|
|
tools/issue-license.py --tier fleet --holder "Some Shop" --seats 25 \
|
|
--expires 2027-08-21
|
|
"""
|
|
import argparse, base64, json, pathlib, sys
|
|
|
|
KEY = pathlib.Path.home() / "agents/hound/.secrets/defs-signing.key"
|
|
KEY_ID = "hound-2026"
|
|
|
|
# Must match hound_api::license::License::canonical exactly.
|
|
def canonical(tier, holder, expires, seats):
|
|
return f"hound-license-v1\ntier={tier}\nholder={holder}\nexpires={expires}\nseats={seats}\n"
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--tier", required=True, choices=["pro", "fleet"])
|
|
ap.add_argument("--holder", required=True, help="display name, e.g. 'Ada L <ada@example.com>'")
|
|
ap.add_argument("--expires", required=True, help="ISO date, e.g. 2027-08-21; the agent falls back to Free after this")
|
|
ap.add_argument("--seats", type=int, default=0, help="Fleet seat count (0 for Pro)")
|
|
ap.add_argument("--key", type=pathlib.Path, default=KEY)
|
|
a = ap.parse_args()
|
|
|
|
if len(a.expires) != 10 or a.expires[4] != "-" or a.expires[7] != "-":
|
|
sys.exit(f"--expires must be an ISO date (YYYY-MM-DD), got {a.expires!r}")
|
|
if "\n" in a.holder or "=" not in canonical("x", a.holder, "", 0).splitlines()[2]:
|
|
sys.exit("--holder must be a single line")
|
|
if a.tier == "fleet" and a.seats < 3:
|
|
sys.exit("Fleet licences have a 3-seat minimum")
|
|
if not a.key.is_file():
|
|
sys.exit(f"no signing key at {a.key}")
|
|
|
|
try:
|
|
from nacl.signing import SigningKey
|
|
except ImportError:
|
|
sys.exit("pip install pynacl")
|
|
seed = a.key.read_bytes()
|
|
if len(seed) == 64:
|
|
seed = seed[:32]
|
|
sk = SigningKey(seed)
|
|
|
|
payload = canonical(a.tier, a.holder, a.expires, a.seats).encode()
|
|
sig = sk.sign(payload).signature
|
|
signed = {
|
|
"payload": base64.b64encode(payload).decode(),
|
|
"signature": base64.b64encode(sig).decode(),
|
|
"key_id": KEY_ID,
|
|
}
|
|
token = base64.b64encode(json.dumps(signed).encode()).decode()
|
|
|
|
print(f"# {a.tier} · {a.holder} · expires {a.expires}"
|
|
+ (f" · {a.seats} seats" if a.seats else ""), file=sys.stderr)
|
|
print(f"# install with: sudo hound license install <token>", file=sys.stderr)
|
|
print(token)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|