flagwake 0.5.0rc1__py3-none-any.whl
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.
- cyberarena/__init__.py +8 -0
- cyberarena/cli.py +210 -0
- cyberarena/rc/baselines.py +702 -0
- cyberarena/rc/cli.py +580 -0
- cyberarena/rc/company.py +788 -0
- cyberarena/rc/conformance.py +329 -0
- cyberarena/rc/data/world.json +97 -0
- cyberarena/rc/data/worlds/fenwick-brae.json +1993 -0
- cyberarena/rc/data/worlds/harlow-pine.json +1045 -0
- cyberarena/rc/demo.py +208 -0
- cyberarena/rc/doctor.py +273 -0
- cyberarena/rc/engine.py +1354 -0
- cyberarena/rc/gen2.py +1702 -0
- cyberarena/rc/generator.py +1146 -0
- cyberarena/rc/golden.py +93 -0
- cyberarena/rc/html.py +403 -0
- cyberarena/rc/mcp.py +240 -0
- cyberarena/rc/mcp_client.py +162 -0
- cyberarena/rc/metrics.py +277 -0
- cyberarena/rc/model_agent.py +493 -0
- cyberarena/rc/packs.py +90 -0
- cyberarena/rc/profile.py +498 -0
- cyberarena/rc/protocol.py +477 -0
- cyberarena/rc/registry.py +678 -0
- cyberarena/rc/registry_cli.py +551 -0
- cyberarena/rc/report.py +551 -0
- cyberarena/rc/rules.py +132 -0
- cyberarena/rc/runner.py +232 -0
- cyberarena/rc/sanity.py +391 -0
- cyberarena/rc/sanity2.py +207 -0
- cyberarena/rc/scoring.py +867 -0
- cyberarena/rc/serve.py +92 -0
- cyberarena/rc/text.py +122 -0
- cyberarena/rc/text2.py +443 -0
- cyberarena/rc/tools_cli.py +183 -0
- cyberarena/rc/toolsapi.py +517 -0
- cyberarena/rc/toolsrun.py +601 -0
- cyberarena/rc/ui.py +1076 -0
- cyberarena/rc/ui_text.py +376 -0
- cyberarena/rc/verify.py +285 -0
- cyberarena/rc/web/css/rc.css +31 -0
- cyberarena/rc/web/css/ui.css +359 -0
- cyberarena/rc/web/js/ui.js +756 -0
- cyberarena/rc/web/js/viewer.js +186 -0
- cyberarena/rc/web/ui.html +279 -0
- cyberarena/rc/web/viewer.html +28 -0
- cyberarena/rc/world.py +267 -0
- cyberarena/rc/world_cli.py +390 -0
- flagwake-0.5.0rc1.dist-info/METADATA +71 -0
- flagwake-0.5.0rc1.dist-info/RECORD +56 -0
- flagwake-0.5.0rc1.dist-info/WHEEL +5 -0
- flagwake-0.5.0rc1.dist-info/entry_points.txt +2 -0
- flagwake-0.5.0rc1.dist-info/licenses/LICENSE +661 -0
- flagwake-0.5.0rc1.dist-info/licenses/LICENSE-DATA +417 -0
- flagwake-0.5.0rc1.dist-info/licenses/NOTICE +15 -0
- flagwake-0.5.0rc1.dist-info/top_level.txt +1 -0
cyberarena/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Flagwake Responder Check: a re-checkable test of how an AI defender behaves during a security
|
|
2
|
+
incident, scored with business cost. The code lives in ``cyberarena.rc``.
|
|
3
|
+
|
|
4
|
+
The package keeps its historical name ``cyberarena``. The generator's random-stream labels start
|
|
5
|
+
with the same word (``cyberarena/rc/1/...``) and must never change: every incident depends on them.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.5.0rc1"
|
cyberarena/cli.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Command-line entry point of ``flagwake``.
|
|
2
|
+
|
|
3
|
+
The Responder Check commands (``check``, ``baselines``, ``replay``, ``doctor``, ``mcp``,
|
|
4
|
+
``serve``) are registered from ``cyberarena.rc.cli``, the registry commands (``init``,
|
|
5
|
+
``defenders``, ``run``, ``view``) from ``cyberarena.rc.registry_cli``; ``verify`` and ``report``
|
|
6
|
+
are defined here.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import textwrap
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
|
|
16
|
+
from cyberarena import __version__
|
|
17
|
+
|
|
18
|
+
DESCRIPTION = (
|
|
19
|
+
"Flagwake Responder Check: a free, offline, re-checkable test of how an AI defender behaves "
|
|
20
|
+
"during a security incident, scored with business cost."
|
|
21
|
+
)
|
|
22
|
+
# The commands as `flagwake --help` lists them: what a newcomer needs first, then the rest.
|
|
23
|
+
GROUPS: tuple[tuple[str, tuple[tuple[str, str], ...]], ...] = (
|
|
24
|
+
("Start here", (
|
|
25
|
+
("demo", "see it work in a few seconds: two example defenders, a report, verify"),
|
|
26
|
+
("ui", "the local page: add a defender, check it, run it, read the result"),
|
|
27
|
+
)),
|
|
28
|
+
("Test a defender", (
|
|
29
|
+
("init", "create flagwake.yaml, the list of defenders, in this folder"),
|
|
30
|
+
("defenders", "add, list, show, remove or test a defender"),
|
|
31
|
+
("run", "run registered defenders on fresh incidents and write reports"),
|
|
32
|
+
("verify", "re-derive every number of a report.json from its stored commands"),
|
|
33
|
+
)),
|
|
34
|
+
("Read results", (
|
|
35
|
+
("view", "open the newest report in the report viewer"),
|
|
36
|
+
("replay", "print one incident of a report as a plain transcript"),
|
|
37
|
+
("baselines", "run the shipped reference programs and print a table"),
|
|
38
|
+
)),
|
|
39
|
+
("Your own company", (
|
|
40
|
+
("world", "write, check or show a company file"),
|
|
41
|
+
)),
|
|
42
|
+
("Other ways in", (
|
|
43
|
+
("check", "a one-off run of a command or endpoint, with no registry"),
|
|
44
|
+
("doctor", "a one-minute smoke test of a model endpoint or a program"),
|
|
45
|
+
("tools", "serve the company as tools (MCP and REST) to a product with its own loop"),
|
|
46
|
+
("mcp", "the check as a stdio MCP server (score its logs with report)"),
|
|
47
|
+
("report", "score MCP transcripts into report.json and report.html"),
|
|
48
|
+
("serve", "serve the report viewer on 127.0.0.1"),
|
|
49
|
+
)),
|
|
50
|
+
) # fmt: skip
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def overview() -> str:
|
|
54
|
+
"""What `flagwake --help` prints: the commands in the order a newcomer needs them."""
|
|
55
|
+
lines = ["usage: flagwake <command> [options]", "", *textwrap.wrap(DESCRIPTION, 92), ""]
|
|
56
|
+
for title, commands in GROUPS:
|
|
57
|
+
lines.append(f"{title}:")
|
|
58
|
+
lines += [f" {name:<11}{text}" for name, text in commands]
|
|
59
|
+
lines.append("")
|
|
60
|
+
lines += [
|
|
61
|
+
"flagwake <command> --help shows a command's options; flagwake --version the version.",
|
|
62
|
+
"Docs: README.md, then docs/QUICKSTART.md.",
|
|
63
|
+
]
|
|
64
|
+
return "\n".join(lines) + "\n"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class _TopParser(argparse.ArgumentParser):
|
|
68
|
+
"""The top-level parser: its help is the grouped overview, not one flat list."""
|
|
69
|
+
|
|
70
|
+
def format_help(self) -> str:
|
|
71
|
+
return overview()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
REPORT_SCHEMA = "flagwake-rc-report-1"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
78
|
+
"""Return the top-level argument parser."""
|
|
79
|
+
parser = _TopParser(prog="flagwake", description=DESCRIPTION)
|
|
80
|
+
parser.add_argument("--version", action="version", version=f"flagwake {__version__}")
|
|
81
|
+
sub = parser.add_subparsers(
|
|
82
|
+
dest="command", metavar="<command>", parser_class=argparse.ArgumentParser
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
from cyberarena.rc.cli import add_subparsers as _rc_add
|
|
86
|
+
|
|
87
|
+
_rc_add(sub)
|
|
88
|
+
from cyberarena.rc.world_cli import add_subparsers as _world_add
|
|
89
|
+
|
|
90
|
+
_world_add(sub)
|
|
91
|
+
from cyberarena.rc.tools_cli import add_subparsers as _tools_add
|
|
92
|
+
|
|
93
|
+
_tools_add(sub)
|
|
94
|
+
|
|
95
|
+
v = sub.add_parser(
|
|
96
|
+
"verify", help="re-derive every number of a report.json from its transcripts"
|
|
97
|
+
)
|
|
98
|
+
v.add_argument("files", nargs="+")
|
|
99
|
+
v.add_argument("--world", help="the company file a custom-world report was made from")
|
|
100
|
+
|
|
101
|
+
d = sub.add_parser(
|
|
102
|
+
"demo",
|
|
103
|
+
help="see it work in a few seconds: two example defenders, a report, verify, and an "
|
|
104
|
+
"edited copy that verify catches",
|
|
105
|
+
)
|
|
106
|
+
d.add_argument("--out", default="flagwake-demo", help="folder to write (default flagwake-demo)")
|
|
107
|
+
d.add_argument("--open", action="store_true", help="open the HTML report in your browser")
|
|
108
|
+
|
|
109
|
+
rp = sub.add_parser("report", help="score MCP transcripts into report.json and report.html")
|
|
110
|
+
rp.add_argument(
|
|
111
|
+
"--mcp-log",
|
|
112
|
+
required=True,
|
|
113
|
+
help="the folder of transcripts written by flagwake mcp --log-dir",
|
|
114
|
+
)
|
|
115
|
+
rp.add_argument("--out", help="the folder to write (default mcp-report)")
|
|
116
|
+
rp.add_argument("--world", help="the company file the transcripts ran in, when it was a file")
|
|
117
|
+
return parser
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _verify_one(path: str, world: str | None = None) -> tuple[list[str], str | None]:
|
|
121
|
+
try:
|
|
122
|
+
with open(path, encoding="utf-8") as f:
|
|
123
|
+
doc = json.load(f)
|
|
124
|
+
except (OSError, json.JSONDecodeError, UnicodeDecodeError) as exc:
|
|
125
|
+
return [f"cannot read: {exc}"], None
|
|
126
|
+
from cyberarena.rc.verify import made_by, verify
|
|
127
|
+
|
|
128
|
+
return verify(doc, world), made_by(doc)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _cmd_verify(args: argparse.Namespace) -> int:
|
|
132
|
+
failed = 0
|
|
133
|
+
for path in args.files:
|
|
134
|
+
errors, note = _verify_one(path, getattr(args, "world", None))
|
|
135
|
+
if errors:
|
|
136
|
+
failed += 1
|
|
137
|
+
print(f"FAIL {path}: " + "; ".join(errors))
|
|
138
|
+
else:
|
|
139
|
+
print(f"OK {path}" + (f" ({note})" if note else ""))
|
|
140
|
+
return 1 if failed else 0
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _rc(handler_name: str):
|
|
144
|
+
def run(args: argparse.Namespace) -> int:
|
|
145
|
+
from cyberarena.rc import cli as rc_cli
|
|
146
|
+
|
|
147
|
+
return getattr(rc_cli, handler_name)(args)
|
|
148
|
+
|
|
149
|
+
return run
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _registry(handler_name: str):
|
|
153
|
+
def run(args: argparse.Namespace) -> int:
|
|
154
|
+
from cyberarena.rc import registry_cli
|
|
155
|
+
|
|
156
|
+
return getattr(registry_cli, handler_name)(args)
|
|
157
|
+
|
|
158
|
+
return run
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _cmd_demo(args: argparse.Namespace) -> int:
|
|
162
|
+
from cyberarena.rc.demo import cmd_demo
|
|
163
|
+
|
|
164
|
+
return cmd_demo(args)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _cmd_tools(args: argparse.Namespace) -> int:
|
|
168
|
+
from cyberarena.rc import tools_cli
|
|
169
|
+
|
|
170
|
+
return tools_cli.cmd_tools(args)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _cmd_world(args: argparse.Namespace) -> int:
|
|
174
|
+
from cyberarena.rc import world_cli
|
|
175
|
+
|
|
176
|
+
return world_cli.cmd_world(args)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
COMMANDS = {
|
|
180
|
+
"verify": _cmd_verify,
|
|
181
|
+
"demo": _cmd_demo,
|
|
182
|
+
"report": _rc("cmd_report_mcp"),
|
|
183
|
+
"check": _rc("cmd_check"),
|
|
184
|
+
"doctor": _rc("cmd_doctor"),
|
|
185
|
+
"replay": _rc("cmd_replay"),
|
|
186
|
+
"baselines": _rc("cmd_baselines"),
|
|
187
|
+
"mcp": _rc("cmd_mcp"),
|
|
188
|
+
"serve": _rc("cmd_serve"),
|
|
189
|
+
"init": _registry("cmd_init"),
|
|
190
|
+
"defenders": _registry("cmd_defenders"),
|
|
191
|
+
"run": _registry("cmd_run"),
|
|
192
|
+
"view": _registry("cmd_view"),
|
|
193
|
+
"ui": _registry("cmd_ui"),
|
|
194
|
+
"world": _cmd_world,
|
|
195
|
+
"tools": _cmd_tools,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
200
|
+
"""Run the CLI and return a process exit code."""
|
|
201
|
+
parser = build_parser()
|
|
202
|
+
args = parser.parse_args(argv)
|
|
203
|
+
if args.command in COMMANDS:
|
|
204
|
+
return COMMANDS[args.command](args)
|
|
205
|
+
parser.print_help()
|
|
206
|
+
return 0
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
if __name__ == "__main__":
|
|
210
|
+
raise SystemExit(main())
|