citadel-shield 3.0.2__tar.gz
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.
- citadel_shield-3.0.2/LICENSE +21 -0
- citadel_shield-3.0.2/PKG-INFO +201 -0
- citadel_shield-3.0.2/README.md +178 -0
- citadel_shield-3.0.2/citadel_shield/__init__.py +33 -0
- citadel_shield-3.0.2/citadel_shield/middleware.py +173 -0
- citadel_shield-3.0.2/citadel_shield.egg-info/PKG-INFO +201 -0
- citadel_shield-3.0.2/citadel_shield.egg-info/SOURCES.txt +61 -0
- citadel_shield-3.0.2/citadel_shield.egg-info/dependency_links.txt +1 -0
- citadel_shield-3.0.2/citadel_shield.egg-info/requires.txt +6 -0
- citadel_shield-3.0.2/citadel_shield.egg-info/top_level.txt +2 -0
- citadel_shield-3.0.2/defense/advanced_mutation_fuzz.py +320 -0
- citadel_shield-3.0.2/defense/canary_manager.py +432 -0
- citadel_shield-3.0.2/defense/citadel_antibody_sync.py +190 -0
- citadel_shield-3.0.2/defense/citadel_bait_seeder.py +260 -0
- citadel_shield-3.0.2/defense/citadel_briefing.py +186 -0
- citadel_shield-3.0.2/defense/citadel_comprehensive_benchmark.py +443 -0
- citadel_shield-3.0.2/defense/confinement_smoke.py +327 -0
- citadel_shield-3.0.2/defense/evidence_truth_smoke.py +285 -0
- citadel_shield-3.0.2/defense/evidence_validation.py +201 -0
- citadel_shield-3.0.2/defense/mandala_kekkai.py +364 -0
- citadel_shield-3.0.2/defense/opencode_kekkai_launcher.py +407 -0
- citadel_shield-3.0.2/defense/practice_audit_battery.py +339 -0
- citadel_shield-3.0.2/defense/sangha.py +4120 -0
- citadel_shield-3.0.2/defense/sangha_citadel_unified_benchmark.py +512 -0
- citadel_shield-3.0.2/defense/sangha_invariant.py +163 -0
- citadel_shield-3.0.2/defense/sangha_iroh_swarm.py +402 -0
- citadel_shield-3.0.2/defense/sangha_kekkai.py +166 -0
- citadel_shield-3.0.2/defense/sangha_merkle.py +171 -0
- citadel_shield-3.0.2/defense/sangha_mesh.py +141 -0
- citadel_shield-3.0.2/defense/sangha_registry.py +277 -0
- citadel_shield-3.0.2/defense/sangha_route.py +415 -0
- citadel_shield-3.0.2/defense/sangha_swarm_benchmark.py +285 -0
- citadel_shield-3.0.2/defense/sangha_vcs.py +1215 -0
- citadel_shield-3.0.2/defense/sangha_watch.py +116 -0
- citadel_shield-3.0.2/defense/test_bait_seeder.py +49 -0
- citadel_shield-3.0.2/defense/test_bounty_ledger.py +181 -0
- citadel_shield-3.0.2/defense/test_citadel_benchmark.py +48 -0
- citadel_shield-3.0.2/defense/test_citadel_briefing.py +31 -0
- citadel_shield-3.0.2/defense/test_citadel_shield.py +300 -0
- citadel_shield-3.0.2/defense/test_gas_trap.py +38 -0
- citadel_shield-3.0.2/defense/test_kernel_sandbox_escape.py +164 -0
- citadel_shield-3.0.2/defense/test_live_sweeper.py +82 -0
- citadel_shield-3.0.2/defense/test_multichain_sweeper.py +145 -0
- citadel_shield-3.0.2/defense/test_pow_gate.py +195 -0
- citadel_shield-3.0.2/defense/test_pqc_claim_boundaries.py +62 -0
- citadel_shield-3.0.2/defense/test_practice_audit_reporting.py +165 -0
- citadel_shield-3.0.2/defense/violet_bounty_ledger.py +615 -0
- citadel_shield-3.0.2/defense/violet_cognitive_tarpit.py +228 -0
- citadel_shield-3.0.2/defense/violet_digital_twin.py +826 -0
- citadel_shield-3.0.2/defense/violet_gas_trap.py +139 -0
- citadel_shield-3.0.2/defense/violet_immunize.py +208 -0
- citadel_shield-3.0.2/defense/violet_insurability_pack.py +140 -0
- citadel_shield-3.0.2/defense/violet_live_sweeper.py +281 -0
- citadel_shield-3.0.2/defense/violet_mesh_event.py +201 -0
- citadel_shield-3.0.2/defense/violet_multichain_sweeper.py +507 -0
- citadel_shield-3.0.2/defense/violet_pow_gate.py +212 -0
- citadel_shield-3.0.2/defense/violet_pqc.py +350 -0
- citadel_shield-3.0.2/defense/violet_spa.py +186 -0
- citadel_shield-3.0.2/defense/violet_stress_test.py +476 -0
- citadel_shield-3.0.2/defense/violet_xdp_spa.py +205 -0
- citadel_shield-3.0.2/defense/wm_decoy_taxonomy.py +111 -0
- citadel_shield-3.0.2/pyproject.toml +43 -0
- citadel_shield-3.0.2/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucas Bailey (WhiteMagic Labs)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: citadel-shield
|
|
3
|
+
Version: 3.0.2
|
|
4
|
+
Summary: Thermodynamic, negative-expected-value (-EV) active cyberdefense framework for modern web and decentralized infrastructure.
|
|
5
|
+
Author-email: Lucas Bailey & WhiteMagic Sangha <ops@whitemagic.dev>
|
|
6
|
+
Project-URL: Homepage, https://github.com/lbailey94/citadel-defense
|
|
7
|
+
Project-URL: Whitepaper, https://github.com/lbailey94/citadel-defense/blob/main/docs/CITADEL_THEORY_AND_ARCHITECTURE.md
|
|
8
|
+
Project-URL: Live Telemetry, https://mcp.whitemagic.dev/defense
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Topic :: Security
|
|
13
|
+
Classifier: Topic :: Internet :: Proxy Servers
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: eth-account>=0.8.0
|
|
18
|
+
Requires-Dist: cryptography>=42.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# The Citadel
|
|
25
|
+
|
|
26
|
+
Experimental security tools for agent workflows: Linux confinement, environment
|
|
27
|
+
filtering, signed single-packet authorization, decoy services, receipt tooling,
|
|
28
|
+
and small Bend algebraic examples. Code is MIT licensed.
|
|
29
|
+
|
|
30
|
+
Start with [audit scope and evidence boundaries](docs/AUDIT_SCOPE.md). The old
|
|
31
|
+
[practice audit report](docs/PRACTICE_AUDIT_REPORT.md) is withdrawn as an assurance
|
|
32
|
+
claim. No independent security audit or production qualification is established
|
|
33
|
+
by this repository's internal checks.
|
|
34
|
+
|
|
35
|
+
## What each part currently demonstrates
|
|
36
|
+
|
|
37
|
+
| Area | Artifact | Evidence boundary |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| Signature experiments | `defense/violet_pqc.py` | Ed25519 plus custom hash-chain code. This is not a complete SLH-DSA implementation, a FIPS conformance claim, or a measured quantum-security margin. |
|
|
40
|
+
| Single-packet authorization | `defense/violet_spa.py`, `defense/violet_xdp_spa.py`, `defense/violet_xdp_spa.c` | The Python benchmark exercises signed knocks, replay/tamper cases, and a Python packet-table simulation. Compiling a BPF ELF does not load it into a kernel. Its Python timings are not NIC/driver drop latency. |
|
|
41
|
+
| Proof-of-work puzzle gate | `defense/violet_pow_gate.py`, `citadel_shield/middleware.py` | HMAC-SHA256 challenge issuance, bounded solution verification and process-local single-use admission. Expected brute-force solving takes 16^difficulty hashes; this does not measure an operator's cost or establish general attack prevention. Solvers run locally in tests. |
|
|
42
|
+
| Web3 sweeper gas-trap | `defense/violet_gas_trap.py` | Deceptive zero-native-gas honeypot wallet and front-running simulation. Dual-hash forensic ledger of captured attacker gas. Simulates mempool counter-drains under controlled conditions. |
|
|
43
|
+
| Monetized lanes & bounty ledger | `defense/violet_bounty_ledger.py` | Tamper-evident dual-hash accounting ledger tracking on-chain gas sweeps, cognitive token burns, forced PoW hashes, and x402 revenue extracted from adversaries. |
|
|
44
|
+
| Comprehensive benchmark | `defense/citadel_comprehensive_benchmark.py` | Six-track micro-benchmark of PoW gates, LLM token sponges, canary minting/verification, Web3 traps, flytrap taxonomy, and SPA knock generation. Metrics reflect local host throughput. |
|
|
45
|
+
| Theory & architecture | `docs/CITADEL_THEORY_AND_ARCHITECTURE.md` | Comprehensive theoretical foundation and operational blueprint detailing biomimetic defense, counter-economics, and line-rate hardware defenses. |
|
|
46
|
+
| Decoys and canaries | `defense/violet_digital_twin.py` | Controlled decoy and token-detection exercises. Detection rates apply to the supplied cases. The historical economic ratio used assumed token and CPU prices; it is not evidence of adversaries' real costs or general defensive advantage. |
|
|
47
|
+
| Replay filters | `defense/violet_immunize.py` | Deterministic rule generation and replay against a finite corpus. No general zero-false-positive guarantee follows. |
|
|
48
|
+
| Peer notifications and chunks | `defense/violet_mesh_event.py`, `defense/sangha_iroh_swarm.py` | Local protocol and chunk-integrity exercises. They do not establish independent multi-host coordination or a deployed network's behavior. |
|
|
49
|
+
| Confinement and airlock | `defense/mandala_kekkai.py`, `defense/sangha_kekkai.py`, `plugins/opencode-citadel-airlock.js` | Experimental filesystem/network restrictions and environment filtering. Qualification requires actual kernel/support identity, readable and writable positive controls, denied negative controls, subprocess startup evidence, and precise runtime integration. |
|
|
50
|
+
| Algebraic examples | `proofs/citta_pac_laws.bend` | Nine Bool/Nat lemmas. These do not prove implementation-level capability attenuation, anti-rollback correctness, safe fallback, or model-weight behavior. |
|
|
51
|
+
| Synthetic folds | `proofs/citta_pac_swarm.bend`, `proofs/cpr_batch.bend` | Fixed-depth U32 arithmetic tree folds. Leaves are not running agents or cryptographically verified receipts. |
|
|
52
|
+
|
|
53
|
+
The seven-ring arrangement is a design organization. It does not mean an
|
|
54
|
+
attacker must defeat every component or that touching one decoy prevents an
|
|
55
|
+
attack. Source modules may retain historical names; use the boundaries above
|
|
56
|
+
when interpreting those names and older reports.
|
|
57
|
+
|
|
58
|
+
## Reproduce a bounded check
|
|
59
|
+
|
|
60
|
+
Requirements: Python 3.10+ and a compatible Bend compiler on `PATH` (the current
|
|
61
|
+
local reproduction used Bend 2.0.39). The report command does not import the
|
|
62
|
+
product modules, read personal keys, or mutate a personal WhiteMagic store.
|
|
63
|
+
Select an output directory outside this checkout to keep your working tree clean:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
python3 defense/practice_audit_battery.py --checks bend --output-dir /tmp/citadel-internal-checks
|
|
67
|
+
python3 -m unittest discover -s defense -p test_practice_audit_reporting.py -v
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
To run the Citadel Comprehensive Benchmark across all defense and counter-economic tracks:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python3 defense/citadel_comprehensive_benchmark.py
|
|
74
|
+
python3 -m pytest -v defense/test_citadel_benchmark.py defense/test_pow_gate.py defense/test_gas_trap.py
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
With `cryptography` installed, `python3 -m unittest discover -s defense -p
|
|
78
|
+
test_pqc_claim_boundaries.py -v` also checks experimental output disclosures,
|
|
79
|
+
an isolated signature/tamper fixture, and failure exit status. It uses temporary
|
|
80
|
+
fixture keys and does not qualify the custom construction for security use.
|
|
81
|
+
|
|
82
|
+
The Bend selection runs both the front-end check and the separate `--verdict`
|
|
83
|
+
kernel recheck. The latter needs the kernel dependencies reported by Bend
|
|
84
|
+
(the local tool requests Lean 4.34.0 or a built `BENDTT` kernel). Missing kernel
|
|
85
|
+
infrastructure is `NOT_EVALUATED`, even if the front-end checker succeeds.
|
|
86
|
+
|
|
87
|
+
The paired JSON/Markdown reports record compiler and source hashes, command
|
|
88
|
+
outputs, process wall times, and synthetic leaves per second calculated from
|
|
89
|
+
the same measurement. Wall time includes compiler checking and startup.
|
|
90
|
+
`PASS` applies to the selected checks only; unsupported lanes remain
|
|
91
|
+
`NOT_EVALUATED` with reasons.
|
|
92
|
+
|
|
93
|
+
Exit status is `0` when all selected checks pass, `1` for an actual failed check,
|
|
94
|
+
and `2` when selected checks cannot be evaluated. Missing engines, matching
|
|
95
|
+
crashes, empty output, and malformed verifier results cannot create parity.
|
|
96
|
+
|
|
97
|
+
For a separate CPR agreement check, first build the Python/Rust/JavaScript
|
|
98
|
+
verifiers according to the selected CPR checkout's own instructions, then pass
|
|
99
|
+
its exact location explicitly:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
python3 defense/practice_audit_battery.py --checks differential --cpr-dir /path/to/continuity-receipt --output-dir /tmp/citadel-cpr-agreement
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This checks the three bundle manifests, honors their anchor options, and
|
|
106
|
+
compares verdicts and error codes against each other and the declared expected
|
|
107
|
+
outcomes. Manifests themselves are not counted as receipt fixtures.
|
|
108
|
+
Agreement between three implementations maintained in the same project is
|
|
109
|
+
useful consistency evidence; an independent oracle and source-to-binary build
|
|
110
|
+
provenance still need separate evidence.
|
|
111
|
+
|
|
112
|
+
## Optional ASGI proof-of-work admission
|
|
113
|
+
|
|
114
|
+
Enable the gate explicitly with `CitadelShieldMiddleware(app, enable_pow_gate=True,
|
|
115
|
+
pow_difficulty=4)`. Difficulty is an integer from 1 through 8, measured in leading
|
|
116
|
+
hexadecimal zeros. The default is 4. Invalid settings or an enabled gate's
|
|
117
|
+
initialization failure stop startup rather than enabling passthrough.
|
|
118
|
+
|
|
119
|
+
Requests under `/api/heavy` receive HTTP 428 and a JSON `challenge`. Solve it with
|
|
120
|
+
`PoWGate.solve(challenge)`, then send `X-Citadel-PoW-Solution` containing a JSON
|
|
121
|
+
object `{"challenge": <the issued object>, "nonce": "<solver nonce>"}`. The
|
|
122
|
+
middleware verifies the signature, transport-peer IP, lifetime, configured
|
|
123
|
+
minimum difficulty and work before calling the application. The header is
|
|
124
|
+
limited to 4096 bytes; duplicate, malformed and invalid solutions receive a new
|
|
125
|
+
challenge. Each challenge admits one request, including when different valid
|
|
126
|
+
nonces are submitted concurrently. Ledger work counts remain estimates rather
|
|
127
|
+
than measured hashes performed by a client.
|
|
128
|
+
|
|
129
|
+
Replay custody is in memory for one `PoWGate` instance and has a bounded cache.
|
|
130
|
+
When full, it refuses new redemptions until entries expire; it does not evict
|
|
131
|
+
live entries to make replay possible. This is not shared multiworker replay
|
|
132
|
+
protection: workers using one signing secret require shared replay custody or
|
|
133
|
+
challenge routing to the issuing instance. Ephemeral fallback secrets also do
|
|
134
|
+
not survive process restarts. Proxy deployments must establish the trusted
|
|
135
|
+
transport peer upstream; this admission path does not trust client-supplied
|
|
136
|
+
`X-Forwarded-For`. No deployed service or outside attack resistance is qualified
|
|
137
|
+
by these local ASGI and gate tests.
|
|
138
|
+
|
|
139
|
+
## Standalone launcher smoke
|
|
140
|
+
|
|
141
|
+
`defense/opencode_kekkai_launcher.py` uses Python's standard library. The
|
|
142
|
+
Linux backend needs x86_64 or arm64 and a kernel exposing Landlock; unsupported
|
|
143
|
+
hosts fail before spawning the child. Run a harmless owned-workspace fixture
|
|
144
|
+
with a deliberately minimal environment:
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
scratch=$(mktemp -d) || exit 1
|
|
148
|
+
workspace="$scratch/workspace"
|
|
149
|
+
mkdir "$scratch/home" "$workspace"
|
|
150
|
+
printf '%s\n' 'owned launcher fixture' > "$workspace/probe.txt"
|
|
151
|
+
env -i PATH=/usr/bin:/bin HOME="$scratch/home" python3 \
|
|
152
|
+
defense/opencode_kekkai_launcher.py --workspace "$workspace" -- \
|
|
153
|
+
/usr/bin/cat "$workspace/probe.txt"
|
|
154
|
+
rm -r -- "$scratch"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The launcher installs filesystem restrictions only. It inherits the child
|
|
158
|
+
environment and leaves networking unrestricted; it does not scrub credentials
|
|
159
|
+
or establish an OpenCode/Devin client integration. The fixture above only
|
|
160
|
+
checks child startup and an allowed read. Use the separate confinement
|
|
161
|
+
positive/negative controls before making a containment claim. macOS has a
|
|
162
|
+
separate Seatbelt path; this Linux fixture does not qualify it.
|
|
163
|
+
|
|
164
|
+
## Demonstrations and integration work
|
|
165
|
+
|
|
166
|
+
[`arena/halo_ctf_visualizer.html`](arena/halo_ctf_visualizer.html) is an illustrative
|
|
167
|
+
animation. Its scripted scores, killfeed, and timing labels are not live
|
|
168
|
+
red-team telemetry. The legacy master benchmark and escape/mutation scripts
|
|
169
|
+
also need review before their printed success labels can serve as acceptance
|
|
170
|
+
criteria; see [the open review list](docs/AUDIT_SCOPE.md).
|
|
171
|
+
|
|
172
|
+
[`docs/OPENCODE_2_1_1_SECURITY_ALIGNMENT_REPORT.md`](docs/OPENCODE_2_1_1_SECURITY_ALIGNMENT_REPORT.md)
|
|
173
|
+
is a maintainer-authored study of a named historical version. The plugin and
|
|
174
|
+
launcher need qualification against the exact host/runtime being used. Installing
|
|
175
|
+
an environment filter alone does not establish protection of credentials,
|
|
176
|
+
transcripts, or every execution path.
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
[MIT](LICENSE) · Copyright (c) 2026 Lucas Bailey / WhiteMagic Labs.
|
|
181
|
+
|
|
182
|
+
### Supported middleware deployment contract
|
|
183
|
+
|
|
184
|
+
Use one process with one `PoWGate` per admission domain. Multiple middleware
|
|
185
|
+
instances in that process may receive the same `pow_gate=` object, sharing
|
|
186
|
+
its atomic replay cache. Separate workers and restarts do not share consumed
|
|
187
|
+
challenges; shared signing keys alone do not solve that problem. A multiworker
|
|
188
|
+
or restart-persistent deployment needs an independently qualified shared atomic
|
|
189
|
+
consumption backend. The default ephemeral signing key invalidates old puzzles
|
|
190
|
+
on restart; do not infer this for an operator-supplied persistent key.
|
|
191
|
+
|
|
192
|
+
`protected_prefixes=("/api/heavy",)` matches that path and its child paths,
|
|
193
|
+
not `/api/heavy-other`. Configure other absolute path prefixes explicitly.
|
|
194
|
+
`admission_counts` exposes process-local outcome counts for application-owned
|
|
195
|
+
metrics; it contains no IPs, request bodies or solution tokens. Both puzzle
|
|
196
|
+
binding and decoy attribution use the ASGI peer. Configure any trusted proxy's
|
|
197
|
+
sanitized peer handling at the ASGI server; client-provided forwarded headers
|
|
198
|
+
are never trusted directly by this middleware.
|
|
199
|
+
|
|
200
|
+
The offline CI uses synthetic puzzles, fixtures and installed-package imports.
|
|
201
|
+
It does not run sweepers, contact targets, use wallets or qualify a live host.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# The Citadel
|
|
2
|
+
|
|
3
|
+
Experimental security tools for agent workflows: Linux confinement, environment
|
|
4
|
+
filtering, signed single-packet authorization, decoy services, receipt tooling,
|
|
5
|
+
and small Bend algebraic examples. Code is MIT licensed.
|
|
6
|
+
|
|
7
|
+
Start with [audit scope and evidence boundaries](docs/AUDIT_SCOPE.md). The old
|
|
8
|
+
[practice audit report](docs/PRACTICE_AUDIT_REPORT.md) is withdrawn as an assurance
|
|
9
|
+
claim. No independent security audit or production qualification is established
|
|
10
|
+
by this repository's internal checks.
|
|
11
|
+
|
|
12
|
+
## What each part currently demonstrates
|
|
13
|
+
|
|
14
|
+
| Area | Artifact | Evidence boundary |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Signature experiments | `defense/violet_pqc.py` | Ed25519 plus custom hash-chain code. This is not a complete SLH-DSA implementation, a FIPS conformance claim, or a measured quantum-security margin. |
|
|
17
|
+
| Single-packet authorization | `defense/violet_spa.py`, `defense/violet_xdp_spa.py`, `defense/violet_xdp_spa.c` | The Python benchmark exercises signed knocks, replay/tamper cases, and a Python packet-table simulation. Compiling a BPF ELF does not load it into a kernel. Its Python timings are not NIC/driver drop latency. |
|
|
18
|
+
| Proof-of-work puzzle gate | `defense/violet_pow_gate.py`, `citadel_shield/middleware.py` | HMAC-SHA256 challenge issuance, bounded solution verification and process-local single-use admission. Expected brute-force solving takes 16^difficulty hashes; this does not measure an operator's cost or establish general attack prevention. Solvers run locally in tests. |
|
|
19
|
+
| Web3 sweeper gas-trap | `defense/violet_gas_trap.py` | Deceptive zero-native-gas honeypot wallet and front-running simulation. Dual-hash forensic ledger of captured attacker gas. Simulates mempool counter-drains under controlled conditions. |
|
|
20
|
+
| Monetized lanes & bounty ledger | `defense/violet_bounty_ledger.py` | Tamper-evident dual-hash accounting ledger tracking on-chain gas sweeps, cognitive token burns, forced PoW hashes, and x402 revenue extracted from adversaries. |
|
|
21
|
+
| Comprehensive benchmark | `defense/citadel_comprehensive_benchmark.py` | Six-track micro-benchmark of PoW gates, LLM token sponges, canary minting/verification, Web3 traps, flytrap taxonomy, and SPA knock generation. Metrics reflect local host throughput. |
|
|
22
|
+
| Theory & architecture | `docs/CITADEL_THEORY_AND_ARCHITECTURE.md` | Comprehensive theoretical foundation and operational blueprint detailing biomimetic defense, counter-economics, and line-rate hardware defenses. |
|
|
23
|
+
| Decoys and canaries | `defense/violet_digital_twin.py` | Controlled decoy and token-detection exercises. Detection rates apply to the supplied cases. The historical economic ratio used assumed token and CPU prices; it is not evidence of adversaries' real costs or general defensive advantage. |
|
|
24
|
+
| Replay filters | `defense/violet_immunize.py` | Deterministic rule generation and replay against a finite corpus. No general zero-false-positive guarantee follows. |
|
|
25
|
+
| Peer notifications and chunks | `defense/violet_mesh_event.py`, `defense/sangha_iroh_swarm.py` | Local protocol and chunk-integrity exercises. They do not establish independent multi-host coordination or a deployed network's behavior. |
|
|
26
|
+
| Confinement and airlock | `defense/mandala_kekkai.py`, `defense/sangha_kekkai.py`, `plugins/opencode-citadel-airlock.js` | Experimental filesystem/network restrictions and environment filtering. Qualification requires actual kernel/support identity, readable and writable positive controls, denied negative controls, subprocess startup evidence, and precise runtime integration. |
|
|
27
|
+
| Algebraic examples | `proofs/citta_pac_laws.bend` | Nine Bool/Nat lemmas. These do not prove implementation-level capability attenuation, anti-rollback correctness, safe fallback, or model-weight behavior. |
|
|
28
|
+
| Synthetic folds | `proofs/citta_pac_swarm.bend`, `proofs/cpr_batch.bend` | Fixed-depth U32 arithmetic tree folds. Leaves are not running agents or cryptographically verified receipts. |
|
|
29
|
+
|
|
30
|
+
The seven-ring arrangement is a design organization. It does not mean an
|
|
31
|
+
attacker must defeat every component or that touching one decoy prevents an
|
|
32
|
+
attack. Source modules may retain historical names; use the boundaries above
|
|
33
|
+
when interpreting those names and older reports.
|
|
34
|
+
|
|
35
|
+
## Reproduce a bounded check
|
|
36
|
+
|
|
37
|
+
Requirements: Python 3.10+ and a compatible Bend compiler on `PATH` (the current
|
|
38
|
+
local reproduction used Bend 2.0.39). The report command does not import the
|
|
39
|
+
product modules, read personal keys, or mutate a personal WhiteMagic store.
|
|
40
|
+
Select an output directory outside this checkout to keep your working tree clean:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python3 defense/practice_audit_battery.py --checks bend --output-dir /tmp/citadel-internal-checks
|
|
44
|
+
python3 -m unittest discover -s defense -p test_practice_audit_reporting.py -v
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
To run the Citadel Comprehensive Benchmark across all defense and counter-economic tracks:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python3 defense/citadel_comprehensive_benchmark.py
|
|
51
|
+
python3 -m pytest -v defense/test_citadel_benchmark.py defense/test_pow_gate.py defense/test_gas_trap.py
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
With `cryptography` installed, `python3 -m unittest discover -s defense -p
|
|
55
|
+
test_pqc_claim_boundaries.py -v` also checks experimental output disclosures,
|
|
56
|
+
an isolated signature/tamper fixture, and failure exit status. It uses temporary
|
|
57
|
+
fixture keys and does not qualify the custom construction for security use.
|
|
58
|
+
|
|
59
|
+
The Bend selection runs both the front-end check and the separate `--verdict`
|
|
60
|
+
kernel recheck. The latter needs the kernel dependencies reported by Bend
|
|
61
|
+
(the local tool requests Lean 4.34.0 or a built `BENDTT` kernel). Missing kernel
|
|
62
|
+
infrastructure is `NOT_EVALUATED`, even if the front-end checker succeeds.
|
|
63
|
+
|
|
64
|
+
The paired JSON/Markdown reports record compiler and source hashes, command
|
|
65
|
+
outputs, process wall times, and synthetic leaves per second calculated from
|
|
66
|
+
the same measurement. Wall time includes compiler checking and startup.
|
|
67
|
+
`PASS` applies to the selected checks only; unsupported lanes remain
|
|
68
|
+
`NOT_EVALUATED` with reasons.
|
|
69
|
+
|
|
70
|
+
Exit status is `0` when all selected checks pass, `1` for an actual failed check,
|
|
71
|
+
and `2` when selected checks cannot be evaluated. Missing engines, matching
|
|
72
|
+
crashes, empty output, and malformed verifier results cannot create parity.
|
|
73
|
+
|
|
74
|
+
For a separate CPR agreement check, first build the Python/Rust/JavaScript
|
|
75
|
+
verifiers according to the selected CPR checkout's own instructions, then pass
|
|
76
|
+
its exact location explicitly:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
python3 defense/practice_audit_battery.py --checks differential --cpr-dir /path/to/continuity-receipt --output-dir /tmp/citadel-cpr-agreement
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This checks the three bundle manifests, honors their anchor options, and
|
|
83
|
+
compares verdicts and error codes against each other and the declared expected
|
|
84
|
+
outcomes. Manifests themselves are not counted as receipt fixtures.
|
|
85
|
+
Agreement between three implementations maintained in the same project is
|
|
86
|
+
useful consistency evidence; an independent oracle and source-to-binary build
|
|
87
|
+
provenance still need separate evidence.
|
|
88
|
+
|
|
89
|
+
## Optional ASGI proof-of-work admission
|
|
90
|
+
|
|
91
|
+
Enable the gate explicitly with `CitadelShieldMiddleware(app, enable_pow_gate=True,
|
|
92
|
+
pow_difficulty=4)`. Difficulty is an integer from 1 through 8, measured in leading
|
|
93
|
+
hexadecimal zeros. The default is 4. Invalid settings or an enabled gate's
|
|
94
|
+
initialization failure stop startup rather than enabling passthrough.
|
|
95
|
+
|
|
96
|
+
Requests under `/api/heavy` receive HTTP 428 and a JSON `challenge`. Solve it with
|
|
97
|
+
`PoWGate.solve(challenge)`, then send `X-Citadel-PoW-Solution` containing a JSON
|
|
98
|
+
object `{"challenge": <the issued object>, "nonce": "<solver nonce>"}`. The
|
|
99
|
+
middleware verifies the signature, transport-peer IP, lifetime, configured
|
|
100
|
+
minimum difficulty and work before calling the application. The header is
|
|
101
|
+
limited to 4096 bytes; duplicate, malformed and invalid solutions receive a new
|
|
102
|
+
challenge. Each challenge admits one request, including when different valid
|
|
103
|
+
nonces are submitted concurrently. Ledger work counts remain estimates rather
|
|
104
|
+
than measured hashes performed by a client.
|
|
105
|
+
|
|
106
|
+
Replay custody is in memory for one `PoWGate` instance and has a bounded cache.
|
|
107
|
+
When full, it refuses new redemptions until entries expire; it does not evict
|
|
108
|
+
live entries to make replay possible. This is not shared multiworker replay
|
|
109
|
+
protection: workers using one signing secret require shared replay custody or
|
|
110
|
+
challenge routing to the issuing instance. Ephemeral fallback secrets also do
|
|
111
|
+
not survive process restarts. Proxy deployments must establish the trusted
|
|
112
|
+
transport peer upstream; this admission path does not trust client-supplied
|
|
113
|
+
`X-Forwarded-For`. No deployed service or outside attack resistance is qualified
|
|
114
|
+
by these local ASGI and gate tests.
|
|
115
|
+
|
|
116
|
+
## Standalone launcher smoke
|
|
117
|
+
|
|
118
|
+
`defense/opencode_kekkai_launcher.py` uses Python's standard library. The
|
|
119
|
+
Linux backend needs x86_64 or arm64 and a kernel exposing Landlock; unsupported
|
|
120
|
+
hosts fail before spawning the child. Run a harmless owned-workspace fixture
|
|
121
|
+
with a deliberately minimal environment:
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
scratch=$(mktemp -d) || exit 1
|
|
125
|
+
workspace="$scratch/workspace"
|
|
126
|
+
mkdir "$scratch/home" "$workspace"
|
|
127
|
+
printf '%s\n' 'owned launcher fixture' > "$workspace/probe.txt"
|
|
128
|
+
env -i PATH=/usr/bin:/bin HOME="$scratch/home" python3 \
|
|
129
|
+
defense/opencode_kekkai_launcher.py --workspace "$workspace" -- \
|
|
130
|
+
/usr/bin/cat "$workspace/probe.txt"
|
|
131
|
+
rm -r -- "$scratch"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The launcher installs filesystem restrictions only. It inherits the child
|
|
135
|
+
environment and leaves networking unrestricted; it does not scrub credentials
|
|
136
|
+
or establish an OpenCode/Devin client integration. The fixture above only
|
|
137
|
+
checks child startup and an allowed read. Use the separate confinement
|
|
138
|
+
positive/negative controls before making a containment claim. macOS has a
|
|
139
|
+
separate Seatbelt path; this Linux fixture does not qualify it.
|
|
140
|
+
|
|
141
|
+
## Demonstrations and integration work
|
|
142
|
+
|
|
143
|
+
[`arena/halo_ctf_visualizer.html`](arena/halo_ctf_visualizer.html) is an illustrative
|
|
144
|
+
animation. Its scripted scores, killfeed, and timing labels are not live
|
|
145
|
+
red-team telemetry. The legacy master benchmark and escape/mutation scripts
|
|
146
|
+
also need review before their printed success labels can serve as acceptance
|
|
147
|
+
criteria; see [the open review list](docs/AUDIT_SCOPE.md).
|
|
148
|
+
|
|
149
|
+
[`docs/OPENCODE_2_1_1_SECURITY_ALIGNMENT_REPORT.md`](docs/OPENCODE_2_1_1_SECURITY_ALIGNMENT_REPORT.md)
|
|
150
|
+
is a maintainer-authored study of a named historical version. The plugin and
|
|
151
|
+
launcher need qualification against the exact host/runtime being used. Installing
|
|
152
|
+
an environment filter alone does not establish protection of credentials,
|
|
153
|
+
transcripts, or every execution path.
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
[MIT](LICENSE) · Copyright (c) 2026 Lucas Bailey / WhiteMagic Labs.
|
|
158
|
+
|
|
159
|
+
### Supported middleware deployment contract
|
|
160
|
+
|
|
161
|
+
Use one process with one `PoWGate` per admission domain. Multiple middleware
|
|
162
|
+
instances in that process may receive the same `pow_gate=` object, sharing
|
|
163
|
+
its atomic replay cache. Separate workers and restarts do not share consumed
|
|
164
|
+
challenges; shared signing keys alone do not solve that problem. A multiworker
|
|
165
|
+
or restart-persistent deployment needs an independently qualified shared atomic
|
|
166
|
+
consumption backend. The default ephemeral signing key invalidates old puzzles
|
|
167
|
+
on restart; do not infer this for an operator-supplied persistent key.
|
|
168
|
+
|
|
169
|
+
`protected_prefixes=("/api/heavy",)` matches that path and its child paths,
|
|
170
|
+
not `/api/heavy-other`. Configure other absolute path prefixes explicitly.
|
|
171
|
+
`admission_counts` exposes process-local outcome counts for application-owned
|
|
172
|
+
metrics; it contains no IPs, request bodies or solution tokens. Both puzzle
|
|
173
|
+
binding and decoy attribution use the ASGI peer. Configure any trusted proxy's
|
|
174
|
+
sanitized peer handling at the ASGI server; client-provided forwarded headers
|
|
175
|
+
are never trusted directly by this middleware.
|
|
176
|
+
|
|
177
|
+
The offline CI uses synthetic puzzles, fixtures and installed-package imports.
|
|
178
|
+
It does not run sweepers, contact targets, use wallets or qualify a live host.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""citadel_shield — Negative Expected Value (-EV) Active Cyberdefense Framework."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# Add defense directory to sys.path
|
|
8
|
+
DEFENSE_DIR = Path(__file__).resolve().parent.parent / "defense"
|
|
9
|
+
if str(DEFENSE_DIR) not in sys.path:
|
|
10
|
+
sys.path.insert(0, str(DEFENSE_DIR))
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from canary_manager import CanaryManager
|
|
14
|
+
from violet_bounty_ledger import BountyLedger
|
|
15
|
+
from violet_digital_twin import ProceduralDigitalTwin
|
|
16
|
+
from violet_gas_trap import GasTrapHoneypot
|
|
17
|
+
from violet_live_sweeper import LiveGasSweeper
|
|
18
|
+
from violet_pow_gate import PoWGate
|
|
19
|
+
except ImportError:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
from citadel_shield.middleware import CitadelShieldMiddleware
|
|
23
|
+
|
|
24
|
+
__version__ = "3.0.2"
|
|
25
|
+
__all__ = [
|
|
26
|
+
"CitadelShieldMiddleware",
|
|
27
|
+
"PoWGate",
|
|
28
|
+
"ProceduralDigitalTwin",
|
|
29
|
+
"CanaryManager",
|
|
30
|
+
"BountyLedger",
|
|
31
|
+
"GasTrapHoneypot",
|
|
32
|
+
"LiveGasSweeper",
|
|
33
|
+
]
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""citadel_shield.middleware — Drop-in ASGI / FastAPI Middleware for Citadel Active Defense.
|
|
2
|
+
|
|
3
|
+
Provides optional decoys and client puzzle admission for Python ASGI applications:
|
|
4
|
+
* Intercepts hostile probe paths (/.env, /wp-admin, /latest/meta-data, /api/v1/namespaces)
|
|
5
|
+
* Serves realistic procedural digital twin decoys with watermarked canaries
|
|
6
|
+
* Requires verified, single-use PoW solutions on /api/heavy routes when enabled
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from fastapi import FastAPI
|
|
10
|
+
from citadel_shield.middleware import CitadelShieldMiddleware
|
|
11
|
+
|
|
12
|
+
app = FastAPI()
|
|
13
|
+
app.add_middleware(CitadelShieldMiddleware, enable_pow_gate=True, pow_difficulty=4)
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import os
|
|
20
|
+
import pathlib
|
|
21
|
+
import sys
|
|
22
|
+
from typing import Callable
|
|
23
|
+
|
|
24
|
+
# Ensure local imports resolve
|
|
25
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent / "defense"))
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
import violet_digital_twin as vdt
|
|
29
|
+
except ImportError:
|
|
30
|
+
vdt = None
|
|
31
|
+
|
|
32
|
+
from defense import violet_pow_gate as vpg
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger("citadel_shield")
|
|
35
|
+
|
|
36
|
+
DEFAULT_DECOY_PATHS = {
|
|
37
|
+
"/.env", "/.env.local", "/.env.production", "/latest/meta-data",
|
|
38
|
+
"/computeMetadata/v1", "/wp-admin", "/wp-login.php", "/xmlrpc.php",
|
|
39
|
+
"/api/v1/namespaces", "/v1.24/containers/json", "/aws/credentials"
|
|
40
|
+
}
|
|
41
|
+
MAX_POW_HEADER_BYTES = 4096
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CitadelShieldMiddleware:
|
|
45
|
+
"""Drop-in ASGI middleware providing automated deception and client puzzles."""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
app: Callable,
|
|
50
|
+
enable_deception: bool = True,
|
|
51
|
+
enable_pow_gate: bool = False,
|
|
52
|
+
pow_difficulty: int = 4,
|
|
53
|
+
decoy_paths: set[str] | None = None,
|
|
54
|
+
state_dir: pathlib.Path | None = None,
|
|
55
|
+
protected_prefixes: tuple[str, ...] = ("/api/heavy",),
|
|
56
|
+
pow_gate: vpg.PoWGate | None = None,
|
|
57
|
+
):
|
|
58
|
+
self.app = app
|
|
59
|
+
self.enable_deception = enable_deception
|
|
60
|
+
self.enable_pow_gate = enable_pow_gate
|
|
61
|
+
self.pow_difficulty = pow_difficulty
|
|
62
|
+
if (not isinstance(protected_prefixes, tuple) or not protected_prefixes or
|
|
63
|
+
any(not isinstance(prefix, str) or not prefix.startswith("/") or
|
|
64
|
+
"?" in prefix or "#" in prefix for prefix in protected_prefixes)):
|
|
65
|
+
raise ValueError("protected_prefixes must be a nonempty tuple of absolute path prefixes")
|
|
66
|
+
self.protected_prefixes = tuple(prefix.rstrip("/") or "/" for prefix in protected_prefixes)
|
|
67
|
+
self.admission_counts: dict[str, int] = {}
|
|
68
|
+
self.decoy_paths = decoy_paths or DEFAULT_DECOY_PATHS
|
|
69
|
+
self.state_dir = state_dir or pathlib.Path(os.environ.get("VIOLET_STATE_DIR", "/tmp/citadel"))
|
|
70
|
+
|
|
71
|
+
self.twin = None
|
|
72
|
+
self.pow_gate = None
|
|
73
|
+
if self.enable_deception:
|
|
74
|
+
try:
|
|
75
|
+
self.twin = vdt.ProceduralDigitalTwin()
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.warning("Could not initialize ProceduralDigitalTwin: %s", e)
|
|
78
|
+
|
|
79
|
+
if self.enable_pow_gate:
|
|
80
|
+
if (type(pow_difficulty) is not int or
|
|
81
|
+
not vpg.MIN_POW_DIFFICULTY <= pow_difficulty <= vpg.MAX_POW_DIFFICULTY):
|
|
82
|
+
raise ValueError("pow_difficulty must be an integer between 1 and 8")
|
|
83
|
+
# An explicitly enabled protection must not silently become passthrough.
|
|
84
|
+
self.pow_gate = pow_gate if pow_gate is not None else vpg.PoWGate()
|
|
85
|
+
|
|
86
|
+
async def __call__(self, scope: dict, receive: Callable, send: Callable) -> None:
|
|
87
|
+
if scope["type"] != "http":
|
|
88
|
+
await self.app(scope, receive, send)
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
path = scope.get("path", "")
|
|
92
|
+
|
|
93
|
+
# 1. Inspect for decoy paths
|
|
94
|
+
if self.enable_deception and self.twin and (path in self.decoy_paths or path.startswith("/.env")):
|
|
95
|
+
# Resolve client IP
|
|
96
|
+
client_ip = scope.get("client", ("127.0.0.1", 0))[0]
|
|
97
|
+
|
|
98
|
+
if path.startswith("/.env"):
|
|
99
|
+
body = self.twin.get_env_file(src_ip=client_ip).encode("utf-8")
|
|
100
|
+
await self._send_response(send, 200, b"text/plain; charset=utf-8", body)
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
if "meta-data" in path or "computeMetadata" in path:
|
|
104
|
+
meta = self.twin.get_aws_metadata(path, src_ip=client_ip)
|
|
105
|
+
body = (json.dumps(meta) if isinstance(meta, dict) else str(meta)).encode("utf-8")
|
|
106
|
+
await self._send_response(send, 200, b"application/json", body)
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
# 2. Explicitly enabled PoW admission on the protected route prefix.
|
|
110
|
+
if self.enable_pow_gate and any(prefix == "/" or path == prefix or path.startswith(prefix + "/")
|
|
111
|
+
for prefix in self.protected_prefixes):
|
|
112
|
+
client = scope.get("client")
|
|
113
|
+
if not client or not isinstance(client[0], str) or self.pow_gate is None:
|
|
114
|
+
await self._send_response(send, 503, b"application/json", b'{"error":"PoW gate unavailable"}')
|
|
115
|
+
return
|
|
116
|
+
# Use the ASGI transport peer. Forwarded headers require a trusted proxy
|
|
117
|
+
# configuration outside this middleware; client-controlled XFF is not identity.
|
|
118
|
+
client_ip = client[0]
|
|
119
|
+
submitted = [value for name, value in scope.get("headers", [])
|
|
120
|
+
if name.lower() == b"x-citadel-pow-solution"]
|
|
121
|
+
verified, reason = False, "missing_solution"
|
|
122
|
+
if len(submitted) == 1:
|
|
123
|
+
if len(submitted[0]) > MAX_POW_HEADER_BYTES:
|
|
124
|
+
reason = "solution_too_large"
|
|
125
|
+
else:
|
|
126
|
+
try:
|
|
127
|
+
solution = json.loads(submitted[0].decode("utf-8"))
|
|
128
|
+
if not isinstance(solution, dict) or set(solution) != {"challenge", "nonce"}:
|
|
129
|
+
raise ValueError("expected challenge and nonce")
|
|
130
|
+
challenge, nonce = solution["challenge"], solution["nonce"]
|
|
131
|
+
if not isinstance(challenge, dict) or type(challenge.get("difficulty")) is not int:
|
|
132
|
+
raise ValueError("invalid challenge")
|
|
133
|
+
if challenge["difficulty"] < self.pow_difficulty:
|
|
134
|
+
reason = "difficulty_below_policy"
|
|
135
|
+
else:
|
|
136
|
+
verified, reason = self.pow_gate.verify_solution(
|
|
137
|
+
challenge, nonce, client_ip, check_replay=True,
|
|
138
|
+
)
|
|
139
|
+
except (ValueError, TypeError, UnicodeError, RecursionError):
|
|
140
|
+
reason = "malformed_solution"
|
|
141
|
+
elif submitted:
|
|
142
|
+
reason = "duplicate_solution_header"
|
|
143
|
+
self.admission_counts[reason] = self.admission_counts.get(reason, 0) + 1
|
|
144
|
+
if not verified:
|
|
145
|
+
challenge = self.pow_gate.generate_challenge(client_ip=client_ip, difficulty=self.pow_difficulty)
|
|
146
|
+
body = json.dumps({
|
|
147
|
+
"error": "PoW challenge required",
|
|
148
|
+
"reason": reason,
|
|
149
|
+
"challenge": challenge,
|
|
150
|
+
"header_required": "X-Citadel-PoW-Solution",
|
|
151
|
+
"solution_format": {"challenge": "the issued challenge object", "nonce": "solver nonce"},
|
|
152
|
+
}).encode("utf-8")
|
|
153
|
+
await self._send_response(send, 428, b"application/json", body)
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
# 3. Allow legitimate request to proceed
|
|
157
|
+
await self.app(scope, receive, send)
|
|
158
|
+
|
|
159
|
+
async def _send_response(self, send: Callable, status: int, content_type: bytes, body: bytes) -> None:
|
|
160
|
+
await send({
|
|
161
|
+
"type": "http.response.start",
|
|
162
|
+
"status": status,
|
|
163
|
+
"headers": [
|
|
164
|
+
(b"content-type", content_type),
|
|
165
|
+
(b"content-length", str(len(body)).encode("ascii")),
|
|
166
|
+
(b"server", b"Citadel-Shield/3.0"),
|
|
167
|
+
(b"x-citadel-defense", b"ACTIVE"),
|
|
168
|
+
],
|
|
169
|
+
})
|
|
170
|
+
await send({
|
|
171
|
+
"type": "http.response.body",
|
|
172
|
+
"body": body,
|
|
173
|
+
})
|