add rea agent module and reverse-engineer archetype
This commit is contained in:
parent
21a217c75c
commit
50c1f2a3d1
15 changed files with 2083 additions and 8 deletions
|
|
@ -103,6 +103,9 @@ box it gets:
|
|||
- **GUI** (opt-in, `services.hyperhive.agent.gui.enable`) — a Wayland
|
||||
desktop with screenshot / keyboard / mouse tools, viewable over VNC in
|
||||
the web UI.
|
||||
- **reverse engineering** (opt-in, `services.hyperhive.agent.rea.enable`, or the
|
||||
`reverse-engineer` archetype) — REA and Ghidra 12.1 in the container, with
|
||||
their MCP tools allowed. → [archetypes](docs/swarm/archetypes.md)
|
||||
- **your own tools** — any extra MCP server (stdio or http) via
|
||||
`services.hyperhive.agent.extraMcpServers`, and any NixOS config you'd
|
||||
put on a machine.
|
||||
|
|
|
|||
|
|
@ -15,10 +15,11 @@ archetype. Picking "none" creates a custom agent, with its own config repo.
|
|||
|
||||
The hyperhive flake ships these:
|
||||
|
||||
| archetype | what it sets |
|
||||
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `reviewer` | permissions `messaging`, `inbox`, `execution`: the default set without `meta`. With `execution` it can run `hive-forge`, `git` and other commands. |
|
||||
| `android-dev` | installs `android-tools` (`adb`, `fastboot`), Gradle and a JDK; default permissions. |
|
||||
| archetype | what it sets |
|
||||
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `reviewer` | permissions `messaging`, `inbox`, `execution`: the default set without `meta`. With `execution` it can run `hive-forge`, `git` and other commands. |
|
||||
| `android-dev` | installs `android-tools` (`adb`, `fastboot`), Gradle and a JDK; default permissions. |
|
||||
| `reverse-engineer` | sets `services.hyperhive.agent.rea.enable`: installs REA, Ghidra 12.1 and procps, and allows all REA MCP tools; default permissions and the agent's normal memory cap. |
|
||||
|
||||
## Where an archetype comes from
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ entry (`type = "stdio" | "http"`, default `"stdio"`): `matrix` and
|
|||
built-in surface and `bash`'s `hive-bash-daemon`. The GUI bridge
|
||||
(`screen`, gated on `services.hyperhive.agent.gui.enable`) is the one
|
||||
entry on the schema's stdio default — it spawns
|
||||
`hive-screen-mcp` fresh each turn. The server name is `hyperhive`, so
|
||||
`hive-screen-mcp` fresh each turn. `rea` (gated on
|
||||
`services.hyperhive.agent.rea.enable`) is a second stdio entry, spawning `rea mcp`. The server name is `hyperhive`, so
|
||||
the tools land in claude as
|
||||
`mcp__hyperhive__<tool>`. Each entry also has an `availableToSubagents`
|
||||
toggle (default `false`) — see
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@
|
|||
frontend
|
||||
reference-docs
|
||||
claude-plugins
|
||||
rea
|
||||
ghidra-12-1
|
||||
;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ in
|
|||
./otel-removed.nix
|
||||
./packages.nix
|
||||
./queue-identity.nix
|
||||
./rea.nix
|
||||
./user.nix
|
||||
./screen.nix
|
||||
./weston-vnc.nix
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@
|
|||
`hive-bash-daemon`, `hive-subagent-daemon`,
|
||||
`hive-forge`, `hive-forge-notify`, `hive-github-notify`,
|
||||
`hive-matrix-daemon`,
|
||||
`hive-metric`, `hive-screen-mcp`) plus the `assets`, `frontend`,
|
||||
`hive-metric`, `hive-screen-mcp`), the `rea` and `ghidra-12-1` packages
|
||||
that `services.hyperhive.agent.rea` installs, plus the `assets`, `frontend`,
|
||||
`reference-docs` and `claude-plugins` trees. Wired by the flake's agent-base
|
||||
nixosModule to `hyperhive.packages.<system>.*`; override an
|
||||
individual key per-agent to swap in a patched binary.
|
||||
|
|
|
|||
63
nix/agent-modules/rea.nix
Normal file
63
nix/agent-modules/rea.nix
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# REA ("Reverse Engineer Anything") for the agent: `services.hyperhive.agent.rea.enable`
|
||||
# puts the `rea` CLI, Ghidra 12.1 (with its JDK) and procps in the container and
|
||||
# registers `rea mcp` as `extraMcpServers.rea` with every tool auto-approved.
|
||||
#
|
||||
# - Ghidra is the pinned nixpkgs' `ghidra-bin` recipe at 12.1.x (see
|
||||
# ../packages/ghidra-12-1.nix); REA rejects the pinned 12.0.4. Both packages
|
||||
# come in through `services.hyperhive.agent.packages` only when this is on, so an
|
||||
# agent without it keeps the shared closure.
|
||||
# - REA's headless Ghidra needs a procps-compatible `ps` on PATH.
|
||||
# - The Ghidra project lives under the agent's state dir. REA needs the `.rep`
|
||||
# directory next to the `.gpr` path to exist, so tmpfiles creates both as the
|
||||
# agent user.
|
||||
# - The JVM heap (`GHIDRA_HEADLESS_MAXMEM`) shares the agent's memory cap
|
||||
# (`agentMemoryMax`); nothing here raises it.
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.hyperhive.agent;
|
||||
userName = cfg.user.name;
|
||||
projectDir = "/agents/${userName}/state/rea";
|
||||
ghidra = cfg.packages.ghidra-12-1;
|
||||
env = {
|
||||
GHIDRA_INSTALL_DIR = "${ghidra}/lib/ghidra";
|
||||
JAVA_HOME = "${pkgs.openjdk21}";
|
||||
REA_GHIDRA_PROJECT_PATH = "${projectDir}/analysis.gpr";
|
||||
GHIDRA_HEADLESS_MAXMEM = "2G";
|
||||
};
|
||||
in
|
||||
{
|
||||
options.services.hyperhive.agent.rea.enable = lib.mkEnableOption ''
|
||||
REA (Reverse Engineer Anything) with Ghidra in the container. Installs `rea`,
|
||||
Ghidra 12.1 and procps, sets `GHIDRA_INSTALL_DIR`, `JAVA_HOME`,
|
||||
`REA_GHIDRA_PROJECT_PATH` (under the agent's state dir) and
|
||||
`GHIDRA_HEADLESS_MAXMEM`, and registers `rea mcp` as
|
||||
`extraMcpServers.rea` with all its tools allowed. Ghidra's 2G heap counts
|
||||
against the agent's normal memory cap
|
||||
'';
|
||||
|
||||
config = lib.mkIf cfg.rea.enable {
|
||||
services.hyperhive.agent.extraMcpServers.rea = {
|
||||
command = "${cfg.packages.rea}/bin/rea";
|
||||
args = [ "mcp" ];
|
||||
inherit env;
|
||||
allowedTools = [ "*" ];
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
cfg.packages.rea
|
||||
ghidra
|
||||
pkgs.procps
|
||||
];
|
||||
environment.variables = env;
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${projectDir} 0700 ${userName} ${userName} -"
|
||||
"d ${projectDir}/analysis.rep 0700 ${userName} ${userName} -"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
@ -31,4 +31,12 @@
|
|||
description = "Builds Android apps: adb and fastboot (android-tools), Gradle and a JDK are installed. It holds the default permissions.";
|
||||
};
|
||||
};
|
||||
reverse-engineer = {
|
||||
module = {
|
||||
services.hyperhive.agent.rea.enable = true;
|
||||
};
|
||||
meta = {
|
||||
description = "Reverse-engineers binaries and applications: REA and Ghidra 12.1 are installed with their MCP tools allowed. It holds the default permissions.";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -218,6 +218,11 @@ in
|
|||
inherit (pkgs) lib;
|
||||
shared = moduleEvalShared;
|
||||
};
|
||||
module-eval-agent-rea = import ./module-eval/agent-rea.nix {
|
||||
inherit pkgs self nixosSystem;
|
||||
inherit (pkgs) lib;
|
||||
shared = moduleEvalShared;
|
||||
};
|
||||
|
||||
# Clippy via crane's first-class `cargoClippy` builder. Reuses the
|
||||
# shared `cargoArtifacts` (deps already built) and runs
|
||||
|
|
|
|||
81
nix/module-eval/agent-rea.nix
Normal file
81
nix/module-eval/agent-rea.nix
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# `checks.module-eval-agent-rea` — see ./lib.nix for the shared
|
||||
# rationale (why this suite exists, naming convention, "evaluates
|
||||
# not executes").
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
self,
|
||||
nixosSystem,
|
||||
shared,
|
||||
}:
|
||||
let
|
||||
inherit
|
||||
(import ./lib.nix {
|
||||
inherit
|
||||
pkgs
|
||||
lib
|
||||
self
|
||||
nixosSystem
|
||||
;
|
||||
})
|
||||
agentWith
|
||||
runGroup
|
||||
;
|
||||
|
||||
# The option is switched on by the archetype that exists for it, so this one
|
||||
# fixture is the "on" arm and the archetype's own wiring.
|
||||
agentRea = agentWith self.hyperhiveArchetypes.reverse-engineer.module;
|
||||
agentNoRea = shared.agentBare;
|
||||
|
||||
rea = agentRea.services.hyperhive.agent.extraMcpServers.rea;
|
||||
pkgsOf = machine: machine.environment.systemPackages;
|
||||
reaTmpfiles = machine: lib.filter (lib.hasInfix "/state/rea") machine.systemd.tmpfiles.rules;
|
||||
in
|
||||
runGroup "agent-rea" [
|
||||
{
|
||||
# `rea` is the presence control for every absence below: the same lookups
|
||||
# find the entry, the variables, the packages and the directories when
|
||||
# the option is on.
|
||||
name = "enabling rea registers its MCP server with every tool allowed and sets Ghidra's environment";
|
||||
ok =
|
||||
rea.allowedTools == [ "*" ]
|
||||
&& rea.args == [ "mcp" ]
|
||||
&& lib.hasSuffix "/bin/rea" rea.command
|
||||
&& lib.all (n: rea.env ? ${n} && agentRea.environment.variables ? ${n}) [
|
||||
"GHIDRA_INSTALL_DIR"
|
||||
"JAVA_HOME"
|
||||
"REA_GHIDRA_PROJECT_PATH"
|
||||
"GHIDRA_HEADLESS_MAXMEM"
|
||||
]
|
||||
&& rea.env.GHIDRA_HEADLESS_MAXMEM == "2G"
|
||||
&& lib.hasPrefix "/agents/a1/state/" rea.env.REA_GHIDRA_PROJECT_PATH;
|
||||
}
|
||||
{
|
||||
name = "enabling rea installs rea and Ghidra 12.1, and creates the agent-owned project directories";
|
||||
ok =
|
||||
let
|
||||
packages = agentRea.services.hyperhive.agent.packages;
|
||||
in
|
||||
lib.all (p: lib.elem p (pkgsOf agentRea)) [
|
||||
packages.rea
|
||||
packages.ghidra-12-1
|
||||
]
|
||||
&& lib.versionAtLeast packages.ghidra-12-1.version "12.1"
|
||||
&& lib.length (reaTmpfiles agentRea) == 2
|
||||
&& lib.all (lib.hasInfix " a1 a1 ") (reaTmpfiles agentRea);
|
||||
}
|
||||
{
|
||||
name = "an agent without rea gets no REA server, no Ghidra variables, no REA packages and no project directories";
|
||||
ok =
|
||||
let
|
||||
packages = agentNoRea.services.hyperhive.agent.packages;
|
||||
in
|
||||
!agentNoRea.services.hyperhive.agent.rea.enable
|
||||
&& !(agentNoRea.services.hyperhive.agent.extraMcpServers ? rea)
|
||||
&& !(agentNoRea.environment.variables ? GHIDRA_INSTALL_DIR)
|
||||
&& !(agentNoRea.environment.variables ? REA_GHIDRA_PROJECT_PATH)
|
||||
&& !(lib.elem packages.rea (pkgsOf agentNoRea))
|
||||
&& !(lib.elem packages.ghidra-12-1 (pkgsOf agentNoRea))
|
||||
&& reaTmpfiles agentNoRea == [ ];
|
||||
}
|
||||
]
|
||||
|
|
@ -149,7 +149,7 @@ let
|
|||
# output and prints it as JSON. A derivation or a path in `meta` would
|
||||
# make that evaluation build or copy something; a function would not
|
||||
# print at all.
|
||||
name = "the built-in archetypes carry a module and a meta of plain data, and reviewer can run commands and android-dev installs its toolchain";
|
||||
name = "the built-in archetypes carry a module and a meta of plain data, and reviewer can run commands and android-dev installs its toolchain and reverse-engineer turns rea on";
|
||||
ok =
|
||||
let
|
||||
plain =
|
||||
|
|
@ -174,7 +174,8 @@ let
|
|||
&& !(lib.elem "meta" reviewer)
|
||||
&&
|
||||
lib.elem pkgs.android-tools
|
||||
(archetypes.android-dev.module { inherit pkgs; }).environment.systemPackages;
|
||||
(archetypes.android-dev.module { inherit pkgs; }).environment.systemPackages
|
||||
&& archetypes.reverse-engineer.module.services.hyperhive.agent.rea.enable;
|
||||
}
|
||||
{
|
||||
# `docs.enable` reads `docs.source`, whose default builds from the
|
||||
|
|
|
|||
|
|
@ -276,6 +276,10 @@ in
|
|||
# is exposed too since it's independently useful/inspectable.
|
||||
inherit swagger-ui-dist swagger-ui-theme;
|
||||
|
||||
# REA and the Ghidra it needs; consumed by nix/agent-modules/rea.nix.
|
||||
rea = pkgs.callPackage ./rea.nix { };
|
||||
ghidra-12-1 = pkgs.callPackage ./ghidra-12-1.nix { };
|
||||
|
||||
# Pre-built system closure every agent container builds from. Exposed as a package
|
||||
# so operators can `nix build .#agent-base-toplevel` (or wire
|
||||
# them into their host system closure via the
|
||||
|
|
|
|||
14
nix/packages/ghidra-12-1.nix
Normal file
14
nix/packages/ghidra-12-1.nix
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{ ghidra-bin, fetchzip }:
|
||||
|
||||
# Ghidra 12.1.4 on the pinned nixpkgs' `ghidra-bin` build recipe. REA accepts
|
||||
# Ghidra >= 12.1 and the pinned nixpkgs ships 12.0.4. Only agents that enable
|
||||
# `services.hyperhive.agent.rea` reference this, so the shared agent closure
|
||||
# keeps the pinned Ghidra.
|
||||
ghidra-bin.overrideAttrs (_: rec {
|
||||
version = "12.1.4";
|
||||
versiondate = "20260921";
|
||||
src = fetchzip {
|
||||
url = "https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_${version}_build/ghidra_${version}_PUBLIC_${versiondate}.zip";
|
||||
hash = "sha256-ZD4Vs1i7BMvrneAbAiOc7F9iBj64Rbom+PaVO//TzK8=";
|
||||
};
|
||||
})
|
||||
1833
nix/packages/rea-package-lock.json
generated
Normal file
1833
nix/packages/rea-package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
57
nix/packages/rea.nix
Normal file
57
nix/packages/rea.nix
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
lib,
|
||||
buildNpmPackage,
|
||||
fetchurl,
|
||||
jq,
|
||||
nodejs_22,
|
||||
}:
|
||||
|
||||
# REA ("Reverse Engineer Anything", npm `rea-agents`) from the published
|
||||
# npm tarball, whose `dist/` is prebuilt: building from the git source runs a
|
||||
# `compile` step that needs `tsrs`, a native compiler with no nix package.
|
||||
#
|
||||
# The tarball carries no lockfile, so ./rea-package-lock.json is vendored: a
|
||||
# `npm install --package-lock-only` over the tarball's `dependencies` alone.
|
||||
# Regenerate it on a version bump. `postPatch` strips the tarball's
|
||||
# `package.json` to the same keys, so the install is production dependencies
|
||||
# only; `frida`, an optional dependency whose install script downloads a
|
||||
# toolchain, is left out with them, and `--ignore-scripts` keeps every other
|
||||
# install script from running.
|
||||
#
|
||||
# Node must satisfy `^22.23.4 || ^24.22.1 || >=26.12.1`; nixpkgs' `nodejs_22`
|
||||
# is the one that does.
|
||||
buildNpmPackage (finalAttrs: {
|
||||
pname = "rea-agents";
|
||||
version = "6.8.0";
|
||||
nodejs = nodejs_22;
|
||||
|
||||
src = fetchurl {
|
||||
url = "https://registry.npmjs.org/rea-agents/-/rea-agents-${finalAttrs.version}.tgz";
|
||||
hash = "sha256-jVQux2qcs1BYS2CmycCIcG/4EtW7WwlMIMlbbcJWmLs=";
|
||||
};
|
||||
sourceRoot = "package";
|
||||
|
||||
postPatch = ''
|
||||
cp ${./rea-package-lock.json} package-lock.json
|
||||
${lib.getExe jq} '{ name, version, license, bin, dependencies }' package.json > package.json.new
|
||||
mv package.json.new package.json
|
||||
'';
|
||||
|
||||
npmDepsHash = "sha256-/YMAZNQHaerVCVUlxTkL2WJyb9FXiu8ZpuBah3fW0+w=";
|
||||
npmFlags = [ "--ignore-scripts" ];
|
||||
dontNpmBuild = true;
|
||||
|
||||
doInstallCheck = true;
|
||||
installCheckPhase = ''
|
||||
runHook preInstallCheck
|
||||
HOME=$(mktemp -d) $out/bin/rea mcp --help
|
||||
runHook postInstallCheck
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Reverse Engineer Anything: an MCP server driving Ghidra and other reverse-engineering tools";
|
||||
homepage = "https://github.com/morluto/rea";
|
||||
license = lib.licenses.mit;
|
||||
mainProgram = "rea";
|
||||
};
|
||||
})
|
||||
Loading…
Reference in a new issue