redKraken hunting corpus
Bundle OKF 0.2 · 3 conceitos · maajix/RedClaude
Open source Repository Open in the app JSON README (API)
About
# redKraken hunting corpus
The shipped Playbook, Skill and maintainer-reference catalogue as a Google Open Knowledge Format v0.2 bundle. Every concept here is derived from the same compiled corpora the runtime reads and is regenerated by `redkraken.okf.build`; nothing in this directory is authored by hand, and nothing in it is loaded by the runtime.
* [playbooks](playbooks/index.md) - 51 Playbooks: what each concludes about, what selects it, and what it owes before a claim moves.
* [skills](skills/index.md) - 6 Skills: the executor half of the graph.
* [references](references/index.md) - 86 maintainer references, one concept each.
Details
- Kind
- OKF bundles
- Topic
- Files & documents
- Publisher
- maajix
- Origin
- okf_github
- Category
- dados
- Version
- 0.2
- Stars
- 1
- Last push
- 2026-09-04T08:15:03Z
- Repository state
- ativo
- Language
- Python
- Added
- 2026-09-08 09:02:43
- Updated
- 2026-09-08 09:02:43
- Origin id
maajix/RedClaude:docs/okf/index.md
README
# RedKraken A local-first bug-bounty hunting harness for web and API targets. The operator runs it on their own machine, against Programs they are authorised to test, and keeps every artifact it produces. This repository is being built one production increment at a time. Today it installs as one Python application and exposes two stable commands: `rk --version` and `rk doctor`. ## Requirements - CPython `>=3.14,<3.15`. `pyproject.toml` and `rk doctor` declare the same range, and starting outside it is a refusal rather than a warning. - No third-party Python production dependencies. The Python runtime is standard library only; any package added later is declared as an exact pin and verified at startup. Operations that invoke a system executable check it at the point of use: TLS interception requires `openssl`, database dump and restore require `pg_dump` and `pg_restore`, and Agent network isolation requires Docker. ## Install From a clean checkout: ```sh python3 -m venv .venv .venv/bin/pip install . .venv/bin/rk --version ``` Without network access, build against the interpreter's own setuptools instead of a downloaded one: ```sh python3 -m venv --system-site-packages .venv .venv/bin/pip install --no-build-isolation --check-build-dependencies . ``` `--check-build-dependencies` keeps the pinned build requirement enforced on this path too; without it, skipping build isolation also skips the check. `rk` is then on the virtual environment's `PATH`. `python3 -m redkraken` runs the identical command line from a source checkout, with `src` on `PYTHONPATH`. ## Check the machine ```sh rk version ``` `rk version` reports what is installed: the package version, the last migration in the corpus, how many there are, and a digest over all of them. Two machines running the same package version can be running different schemas, so the digest is the number that decides whether they agree. `rk --version` answers the same question as one line for a person; this answers it in the shape every other command answers in. It reaches no database and no network. ```sh rk doctor ``` `rk doctor` reports whether this machine can be trusted to run a Program. It reads; it creates no state, sends no traffic and starts no run. Its result is JSON on stdout: the application version, the interpreter version and the supported range, one entry per readiness assertion, and every violation it found. Give it a Program configuration to validate that too: ```sh rk doctor --config program.toml ``` ## The Agent boundary Every command that starts a container -- `rk run`, `rk tool run`, `rk browser run` -- reads the same five variables and refuses if any is unset. Nothing is defaulted: an image name guessed here would start a child in whatever the guess matched, and a proxy URL guessed here would point a child's only route at whatever answers on that port. | Variable | What it names | | -------- | ------------- | | `RK_AGENT_IMAGE` | The image children run in. Never pulled implicitly. | | `RK_AGENT_NETWORK` | A Docker network created `--internal`, whose only peer is the door. | | `RK_AGENT_PROXY_CONTAINER` | The door's container name on that network. | | `RK_AGENT_PROXY_URL` | `http://<container>:<port>`, uncredentialed. Children get it as `HTTP_PROXY`, and the door binds the port it names. | | `RK_PROXY_CA_FILE` | The certificate children verify every target against: `<authority>/ca.pem`. | Three more are optional and absent by default, because absent is the contained value: `RK_AGENT_APPLICATION`, `RK_AGENT_SDK` and `RK_AGENT_HOME` name the application, the SDK and the home mounted inside a child. A container with no home mounted has no credential at all rather than somebody else's. The home is a template: each run is handed a copy of it, and the copy is removed when the run ends, so what one child writes is never what the next child reads. Claude authentication is a setup-token file on the supervisor, at `~/.config/redkraken/claude-oauth-token` by default or the absolute path in `RK_AGENT_OAUTH_TOKEN_FILE`. Run `tools/setup-agent-oauth.sh` for the one human `claude setup-token` step and the Doctor/canary checks around it. The token is read only by the supervisor and sent in the private stdin envelope of the short-lived child; it is never copied or mounted. An existing `.claude/.credentials.json` is ignored and left untouched. ### Satisfying them `rk proxy door` puts the production fence on the Agent network as its only peer, and returns while it is still running. It takes no flags for the five variables above -- a second place to say them is a day the boundary verifies against a door no child was pointed at -- and reads three more of its own: | Variable | What it names | | -------- | ------------- | | `RK_PROXY_DATABASE_URL` | The door's own connection, held as `rk2_proxy`. Not `RK_DATABASE_URL`: a fence running as the runtime is a fence with the privileges of the thing it fences. | | `RK_ARTIFACT_ROOT` | Where exchanges are filed. | | `RK_PROXY_AUTHORITY` | Where the door mints and keeps its signing material. | | `RK_ARTIFACT_KEY` | Optional. A **file**, not an `op://` reference: resolving one inside the container would mean a service-account token inside the container. Without it, sealed responses are refused. | The door runs as `65534:65534` with every capability dropped and a read-only root filesystem, so both directories it writes have to be writable by that user. It creates neither -- a directory this command made would be owned by the operator, which is the state it refuses on the next line: ```sh mkdir -p /var/lib/rk2/artifacts /var/lib/rk2/authority sudo chown 65534:65534 /var/lib/rk2/artifacts /var/lib/rk2/authority docker network create --internal rk2-agent docker network create rk2-egress export RK_AGENT_IMAGE=rk2-agent:local export RK_AGENT_NETWORK=rk2-agent export RK_AGENT_PROXY_CONTAINER=rk2-door export RK_AGENT_PROXY_URL=http://rk2-door:18080 export RK_PROXY_CA_FILE=/var/lib/rk2/authority/ca.pem export RK_PROXY_DATABASE_URL="postgres://rk2_proxy:...@host.docker.internal:5432/rk2" export RK_ARTIFACT_ROOT=/var/lib/rk2/artifacts export RK_PROXY_AUTHORITY=/var/lib/rk2/authority rk proxy door ``` The door runs `python3 -m redkraken.door` inside `RK_AGENT_IMAGE`, from this checkout mounted read-only, so that image needs a Python 3 and an `openssl` on its `PATH` -- the second is what mints the authority. The connection string is written from the container's point of view, which is why the example says `host.docker.internal`: the door is given that name explicitly, because a Postgres on this machine has no other spelling from inside a container. Everything decidable without starting anything is decided first: the boundary is described, both directories are writable by the user the door runs as, the key is a file and not a reference, `RK_PROXY_CA_FILE` names the authority this door will sign with, no container already holds the door's name, and both networks are what they claim -- the Agent's internal and empty, the egress one routable and empty. Then the door starts on its egress attachment alone -- so it has a database to reach before it is anywhere a child could see it -- and joins the Agent network only once it says it is serving. The door is on two networks and the difference between them is the point. The Agent network carries no route anywhere: no database, no internet, no host. The second attachment carries both, and nothing but the door is on it, so a child reaches the internet only by asking the door to go -- which is the same thing as saying the fence sees every request. `--egress` names that second network (`rk2-egress` by default) and `--timeout` how long the door gets to bind and open its fence before it is given up on and taken away. That second network is a network of the operator's own rather than the engine's default `bridge`, and the door refuses to start on one that already has peers. The door binds every interface it has, so a peer on the way out is a peer that could reach the fence without a capability ever having been minted for it. The door outlives the command that started it, and is not removed when it fails: a door that vanished would take the only account of why with it. So `rk proxy door` refuses to start where one already exists, and taking it away is the operator's own `docker rm --force rk2-door`. The command asserts the topology from out here, where an engine exists. A process inside a container cannot enumerate the peers of the networks it is on, and a door holding an engine socket would be a worse hole than the one this closes -- so `python3 -m redkraken.door` run by hand somewhere else binds wide on a network nobody vouched for. The door is only the door when `rk proxy door` is what put it there. `rk proxy serve` is the other door: the one an operator runs in a terminal, on loopback. It refuses a routable bind, because what arrives at a listening fence is bearer material anybody who can reach the port may spend. The contained door is the one exception, and only because the whole of what can reach its port is the child the capability was minted for. ## The Program configuration A configuration is versioned and declarative. The schema is closed: an unrecognised key is a refusal, not an ignored line. Absent permission is denial, so a rule of engagement that is not set is off. ```toml schema_version = 1 [program] name = "acme-web" platform = "hackerone" [rules_of_engagement] mutation = true [budgets] requests = 5000 tokens = 2000000 run_tokens = 40000 run_requests = 50 lane_tokens = 500000 lane_requests = 1000 concurrency = 2 burst = 500 window_seconds = 3600 [[scope.include]] host = "app.example.com" ports = [443] protocols = ["https"] paths = ["/api/"] [[scope.exclude]] host = "admin.example.com" ports = [443] protocols = ["https"] paths = ["/"] [[identity]] name = "member" class = "user" slot_ref = "slot://identity/member" [[required_header]] name = "X-Bounty-Id" value_ref = "slot://header/bounty-id" [[callback]] name = "oob-dns" kind = "dns" host = "oob.example.net" ``` The nine budget limits state three nested allowances, and the nesting is checked: an Agent run is spent inside a Lane and a Lane inside the campaign, so `run_tokens` above `lane_tokens` or `tokens`, or `run_requests` above `lane_requests` or `requests`, is refused by name. Only the per-run ceiling is compared upwards. A Lane allowed more than the campaign holds is slack rather than a contradiction, because the campaign total binds first and the Lane never does. A scope entry names a hostname, an address, an address range in CIDR notation such as `93.184.216.0/24`, or a wildcard such as `*.example.com`. A range must be globally routable, the same rule a bare address is held to. An inclusion's wildcard must name at least two labels of its own, so `*.com` is refused, and an inclusion's range must be at least a `/16` in IPv4 or a `/32` in IPv6, so `1.0.0.0/8` and `2000::/3` are refused. Both are floors rather than public-suffix rules or width judgements: `*.co.uk` and a `/16` both pass, a registry's block does not, and how wide an inclusion may be under the floor remains the operator's judgement against the Program. An exclusion has no floor, because breadth there withdraws authority rather than claiming it. Hosts are compared in one spelling — lowercased, without a trailing root dot, addresses in canonical form — so two ways of writing the same Program produce the same hash, and a repeated rule counts once. A path names a prefix on the host that carries it. It begins with a single forward slash, so the protocol-relative `//elsewhere.example/admin` is refused; it holds no `..` segment and no unprintable character, so what is printed is what was matched. Opening a Program records one Application per inclusion and opens one `recon` Task against each, so a campaign has something to rank on its first pass. A protocol, a host, a port and a path prefix are an address, and an address is what a Task can be sent to — which is why an inclusion naming two protocols is two Applications, and why an inclusion naming a wildcard records nothing: `*.example.com` names a set of hosts and no address. Both counts are reported as `first_tasks`, and rerunning an unchanged configuration records nothing and opens nothing, because the Task each subject already carries is the one being resumed. An identity also declares its `class`, either `user` or `privileged`, defaulting to `user`. Whether a credential is an administrator's is a provisioning fact the operator knows and no response can reveal, so it is stated here rather than inferred. Secret material is never written into a configuration. An identity carries a `slot_ref` and a required header carries a `value_ref`. Both name a runtime-owned slot — the `slot://` scheme and nothing else, so a configuration cannot smuggle its own credential in a URL — which the runtime resolves and the proxy injects. A key that would hold a secret inline is refused by name. Diagnostic output follows the same rule. It reports names, counts, controls, versions and the two hashes that identify a configuration — `source_sha256` over the file as written and `canonical_sha256` over its normalised content, so reformatting does not change the policy's identity — and never a reference or a header value. ## The operator console ```sh rk ui serve --config program.toml ``` The console is the same operations this CLI already exposes, rendered as pages. Every view is one of the reads — `rk ui read` for the Program's lifecycle and integrity, its slates, runs, leases, budgets, findings, chains and the documents on file; `rk state` for the model's own records; `rk decision list` for what is waiting on a person; `rk report` for one rendering by label — and every button is one of the operator verbs, so a page and a command cannot come to mean different things. It holds no query of its own. Three connection strings, because the console is three roles and not one. The panels read as the runtime, the record index reads as the agent because that is whose isolation it is describing, and halt, resume, the two queue verbs, the report and the gate clearance run as the operator. A console given one string in all three places renders every page and can lift nothing. It listens on loopback, refuses a `Host` or `Origin` header that is not the address it was given, and puts a token this process alone holds on every form, so a page in another tab cannot submit a verb to it. There is no login, because there is no remote: the authority is the operator connection string, and anyone who can reach the socket already has the machine. ```sh rk ui read --config program.toml ``` The same panels without a browser, in the shape every other command answers in. Each one is bounded and says how many rows it did not return. ## Outcomes `rk doctor` aggregates: it reports every violation it found, not the first, and exits on the most fundamental one. A schema version this build cannot read is the one exception, reported alone because it explains every other refusal such a document would draw. | Exit | Meaning | | ---- | ------- | | `0` | Ready. | | `1` | A refusal this build cannot classify. | | `2` | The command line could not be understood. | | `3` | `invalid_configuration`: the configuration was refused. | | `4` | `unsupported_version`: an interpreter or schema version is out of range. | | `5` | `missing_dependency`: a required module or pinned distribution is absent. | ## Tests The suite is standard library only and runs offline: ```sh python3 -m unittest discover -q python3 tools/check_baseline.py python3 -m tools.check_dispositions python3 -m tools.check_coverage python3 -m tools.check_intake ``` The third is the migration ledger: for each of the 223 artifacts the census froze, what became of it. A row either names something this checkout has and cites the file that proves it works, or names the open migration ticket committed to building it -- and fails the moment that ticket is marked resolved with the thing still missing. The fourth runs the third and then measures the shape of the answer, which no single row can show: the 49 in-scope Playbooks are all present, loadable by one role and registered in the schema at the exact text this checkout ships; the 73 operator references and 9 sink packs are each attached to one Skill or Playbook that declares them and nothing sits loose in a `references/` directory; and the 52 retirements split by kind under a scope whose reversal is on record. The fifth is the technique intake: one row per public source read, carrying its digest, the Property class it maps to and what it produced. Producing nothing is an outcome with a reason rather than a gap, a row cannot claim a fixture for a class the schema records as not agent-makeable, and a fixture whose provenance cites the intake ticket and which no row produced fails the gate. The composed suite is the same modules with a server and a container engine behind them. It needs a PostgreSQL 18 superuser URL and, for the container cases, the two images: ```sh RK_TEST_SUPERUSER_URL=postgres://postgres:...@127.0.0.1:5432/postgres \ RK_TEST_CONTAINERS=1 \ python3 -m unittest discover -q ``` Without `RK_TEST_SUPERUSER_URL` the database cases skip; without `RK_TEST_CONTAINERS=1` the boundary cases skip. `RK_TEST_AGENT_IMAGE` and `RK_TEST_BROWSER_IMAGE` name the images those cases run in. ## The release gate The suites measure a checkout. The release gate measures the artifact: ```sh python3 -m tools.release_gate --superuser-url postgres://postgres:...@127.0.0.1:5432/postgres ``` It exports `HEAD` with `git archive` into a scratch directory, installs it there through the documented offline path, and then does everything else with that installation rather than with this checkout. Six stages, in order, each using what the one before it built: * `export` — the commit and nothing lying next to it. * `install` — the offline install, then what it left behind: only `redkraken` and `pip` in the environment, a distribution that declares no requirement of its own, none of the checkout-only directories inside the installed package or beside it (`docs`, `prototype`, `scratch`, `tmp`, `tests`, `tools`, `.git`, `.venv`), and `rk --version`, `rk version` and `rk doctor` all answering from a build the application recognises as its own. * `database` — provision, migrate, status, verify, migrate again, run, read, dump, restore into a second database, verify that, and open the same Program on the restored copy to prove it resumes rather than starts over. The second migrate is the upgrade reading and it is judged on what it applied, not on whether it succeeded: an installation that reapplied the corpus over a current database reports the same `ok`. * `topology` — the internal Agent network, a routable egress network and the door joined to both by `rk proxy door`, with the Agent network holding the door and nothing else. * `privileges` — ticket 66's standing check on both databases, the migrated one and the restored one. * `suites` — the offline suite and the composed suite, each twice, run from the export against the installed application. Twice because the second run is what proves the first left the server as it found it. A suite exits zero when it skips everything, so the counts are read too: both runs of a suite must select the same tests, and the composed run must skip fewer of them than the offline run did. `--stage NAME` selects a stage (repeat it for several), `--keep` leaves the build directory and the databases behind, and the two together are how a single stage gets iterated on. A selection that names a stage whose input nothing built is refused at once, naming the stage that builds it: `--root` then has to point at a directory a kept run already left behind. `--keep` also leaves the generated role passwords in `roles.json` under that directory, readable only by the user that ran the gate; a kept root is a credential and should be removed with the databases it belongs to. Nothing the gate runs inherits the calling environment; every child gets `PATH`, `HOME`, `TMPDIR` and `LANG` written from scratch, so neither a provider key nor a database URL exported in the shell that starts it can reach the installation being measured. The `topology` stage needs a container engine and the Agent image, and fails rather than skipping when either is missing. The superuser URL must point at a server that can be dropped: the gate creates, drops and recreates `rk2_release_gate`, `rk2_release_gate_restored` and `rk2_gate_suite`. ## The release audit The gate measures the artifact. The audit measures the Spec: ```sh python3 -m tools.check_audit python3 -m tools.check_audit --run ``` `baseline/spec-verification.tsv` holds one row per requirement -- 230 user stories, 19 Implementation Decisions, 24 Testing Decisions, 9 Out-of-Scope constraints, the 6 release conditions under Further Notes and the 7 registered prototype regressions -- and each row names the tickets that built it and the tests or gates that check it. The column is `verification` rather than `evidence`: Evidence is the role an observation plays for a claim, which `CONTEXT.md` reserves, and the v1 ledger beside this one already spells the same idea the same way. The audit reads the Spec, the tracker and that table together and refuses: * a requirement with no row, a row for a requirement the Spec does not state, or a requirement stated twice, since the weaker of two answers is the one nobody reads; * a row whose digest no longer matches the requirement's own text, so a story reworded after somebody mapped it stops matching; * a row naming a ticket that does not exist or is not resolved, or verification that is neither a test this checkout can run nor a gate it ships -- a case holding no test is not one, because `unittest` loads it to an empty suite -- there is no third kind, which is how a citation to a document is refused rather than counted, and this gate may not be cited as its own evidence; * a requirement whose evidence is *owed* -- `owed:64`, the open ticket that will produce it -- where that ticket is finished, does not exist, or where the release outcome has been resolved with the row still saying it. Two rows say it today: the final review and final acceptance are ticket 64's and 65's, and a map that cited something else for them would be citing something that does not check them; * a Spec section nobody reads: the seven headings are frozen, so a requirement arriving under a new one is release-blocking rather than invisible; * a ticket in 01 through 63 -- the ticket that wrote this gate included -- that is unresolved, blocked by unfinished work, or has no revision resolving it -- the commit that wrote its resolved status -- and any acceptance box it left unticked without naming an open ticket that closes it; * a dependency graph with a cycle, a blocker nobody wrote, or a resolved ticket with no path to the release outcome, which is what tickets raised beside the plan look like until whichever ticket owns their outcome names them; * a named area of the release -- the runtime, the agents, the Skills, the 49 Playbooks, the operator surface, the v1 import, long-session recovery and the first hunt -- holding no requirement at all, or holding requirements none of which is checked by that area's anchor; * a registered prototype regression whose map does not name the tickets the registry says it requires. `--run` then executes every cited test and every cited gate. A failure, an error *and a skip* are all refusals: most of the live arms stand down without a database or a container, and a citation that stood down proves nothing about the requirement citing it. So the run mode is a composed-suite command -- it wants `RK_TEST_SUPERUSER_URL`, `RK_TEST_CONTAINERS=1`, the two images and the Agent SDK installed in the interpreter, exactly like the composed suite. It wants the SDK at the one version the runtime is measured against, the pair `KNOWN_RUNTIME` names; any other version is an unmeasured runtime, which every agent citation refuses on purpose. The one skip it accepts is the inverse case, and the suite says so in its own words: a test that requires the runtime to be *absent* cannot run where this mode requires it to be present. The one citation it does not run is `gate:tools.release_gate`, which builds an install and provisions two databases and is reported as deferred. Run it on an idle machine. The cited set includes the surface benchmarks, and a benchmark measures wall time in the process the audit is already running everything else in: on a loaded host a median drifts over its budget and the audit reports the drift as a failed citation, which is the honest reading of a measurement taken under load rather than a fault in the requirement. What it does not measure is whether the cited evidence is any good; a test that asserts nothing would satisfy it. That is the final code review's job. And it is not one of the gates the release gate runs inside its export: it reads this repository's history for the commit that resolved each ticket, and a tarball committed once as a checkout would answer that with one synthetic revision for every ticket in the plan.