Sprint 10 — verify_tls default flip + /graphql trailing slash : deux fixes qui débloquent la première connexion réelle contre un Mythic auto-hébergé.
Fix 1 — verify_tls default
Mimic est un BAS lab tool dont le workflow dominant est un Mythic auto-hébergé avec certificat self-signed. L'ancien default verify_tls=True à chaque couche cassait silencieusement la première connexion Test (CERTIFICATE_VERIFY_FAILED) tant que l'opérateur n'avait pas remarqué la checkbox. Sprint 10 aligne le default sur le workflow réel.
Helper text durci : "Uncheck only for lab Mythic with self-signed certificates. Disabling verification exposes the API token to MITM." — tradeoff sécu spell-out direct, opt-in conscient.
Migration 0008 : server_default flippé à sa.false() via op.batch_alter_table (SQLite). Round-trip upgrade → downgrade → upgrade testé. Les lignes existantes ne sont pas mutées (valeur stockée préservée).
urllib3 spam suppression : urllib3.disable_warnings(InsecureRequestWarning) gated sur not verify_tls dans MythicAdapter.__init__. Pas de suppression module-level.
Fix 2 — /graphql → /graphql/ (HTTP 301 fix)
Bug surface en user testing avec un Mythic réel à https://192.168.10.60:7443 : test_connection retournait HTTP 301. Cause confirmée à 92 % par un workflow diagnostic (lecture adapter + WebFetch source canonique Mythic_Scripting), puis confirmée par l'utilisateur via curl -kv (Location header : /graphql/).
Notre adapter sprint 8 a hardcodé "/graphql" sans slash final. Le client officiel mythic_utilities.get_http_transport utilise f"{...}:{port}/graphql/" (slash REQUIS). Nginx Mythic 3.x retourne 301 pour la canonicalisation.
Le allow_redirects=False du sprint 8 (défense SSRF intentionnelle, gardée) faisait surface le 301 raw au lieu de le suivre.
Fix : 1 caractère dans mythic.py:119 + maj du commentaire de pinning + 3 fichiers de tests + 1 nouveau test de régression.
Reviews : code-review APPROVED + 1 NIT non-bloquant ; design-review APPROVED (0 finding) ; spec-review APPROVED post-amendement (SPEC.md:85 ligne du default mise à jour).
Test plan
Backend : 469 / 469 pytest (a grandi de 1 — nouveau test de régression test_mythic_adapter_url_has_trailing_slash). ruff + mypy --strict clean.
Migration round-trip : alembic upgrade head → downgrade -1 → upgrade head sur SQLite fresh, OK.
E2E : non re-joué (mur de credentials Vite proxy persistant). Test manuel ci-dessous.
Comment tester en local
# Avec la branche checkout et MIMIC_ENCRYPTION_KEY set :exportMIMIC_ENCRYPTION_KEY=$(python3 -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')exportMIMIC_C2_ADAPTER=fake # pour tester sans Mythic live
make build && make start
make create-admin USER=alice PASS=changeme8 # premier setup# http://127.0.0.1:5000
Scénarios :
Default vérifié à la création — ouvre un engagement en édition → carte "C2 configuration" → la checkbox "Verify TLS certificate" est décochée par défaut, helper text MITM visible juste en-dessous.
Test connection avec self-signed (cas dominant) — colle l'URL d'un Mythic self-signed https://<lab>:7443, paste un token, laisse la box décochée, Save → Test connection → réponse { ok: true } (FakeAdapter répond toujours OK, en prod-live le cert self-signed n'est plus rejeté). Tail backend stderr : 0 InsecureRequestWarning (suppression engagée).
Verify forced (cas explicite prod) — recoche la box, Save, Test connection contre un cert self-signed → réponse { ok: false, error: "<SSLError CERTIFICATE_VERIFY_FAILED>" }. Prouve que le flag est toujours honoré bout-en-bout.
Migration — sur une DB fresh : alembic upgrade head puis pragma table_info(c2_config) (SQLite) → verify_tls default 0. Puis INSERT INTO c2_config (engagement_id, url, api_token_encrypted) VALUES (1, 'https://x', 'y') → la row aura verify_tls = 0.
Rows existantes intouchées — une row sauvegardée avec verify_tls = 1 AVANT cette migration reste à 1. L'opérateur qui veut basculer doit explicitement re-Save la config avec la box décochée.
RBAC unchanged — un user SOC reçoit toujours 403 sur /api/engagements/<id>/c2-config*. Aucun changement.
assertion is False (renamée default_false) ; tests qui PUT verify_tls=True explicitement laissés intacts
frontend/src/components/C2ConfigCard.tsx
useState(false) + delete-reset false + helper text MITM en text-graphite mirror FormField hint
Security tradeoff acknowledged
Default sécu-relevant flippé en pleine conscience :
Mimic est un BAS / red-team lab tool dont le 99e percentile d'usage est Mythic self-signed.
Toggle reste visible et opt-in, helper text spell-out le risque MITM directement.
Decision lockée dans tasks/todo.md § Decisions + ce PR body.
Plugin security-guidance@claude-code-plugins a flagged 3 fois (HIGH × frontend useState, HIGH × API fallback, MEDIUM × adapter/factory/model/migration). Conditions de "proceed without changes" du plugin elles-mêmes remplies : user a explicitement demandé le flip + tradeoff surfacé dans l'UI.
Rows existantes verify_tls=truenon mutées par la migration — defense in depth pour qui aurait déjà bouclé sa config en mode strict.
test_c2_config.py:209 — renamer test_get_config_verify_tls_default_false en test_get_config_verify_tls_roundtrip_false (le test PUT explicitement la valeur, ne teste pas le default ORM). Optionnel ; cosmétique.
mythic.py:122-125 — urllib3.disable_warnings est process-global. Alternative cleaner : warnings.catch_warnings() dans _post(). Plus de code pour bénéfice marginal (aucun autre caller urllib3 dans Mimic). Skipped.
Symétrie test_migration_0008_c2.py absent vs 0006/0007 ont chacun le leur. 0008 est 10 lignes, round-trip déjà confirmé manuellement, faible risque. Skipped.
Commits
c31f985 docs(sprint-10): plan flip verify_tls default to false (redteam-friendly)
774ae05 fix(frontend): default verify_tls checkbox to unchecked + add helper text
ee5fda6 fix(frontend): make verify_tls helper text spell out the MITM tradeoff
0fab40b feat(backend): default verify_tls to false for lab/redteam Mythic
91533c2 docs(spec): align c2_config.verify_tls default with sprint 10 (false)
208c6e6 fix(c2): append trailing slash to mythic /graphql URL to avoid 301 redirect
## Summary
- **Sprint 10 — verify_tls default flip + /graphql trailing slash** : deux fixes qui débloquent la première connexion réelle contre un Mythic auto-hébergé.
### Fix 1 — `verify_tls` default
Mimic est un **BAS lab tool** dont le workflow dominant est un Mythic auto-hébergé avec certificat **self-signed**. L'ancien default `verify_tls=True` à chaque couche cassait silencieusement la première connexion Test (CERTIFICATE_VERIFY_FAILED) tant que l'opérateur n'avait pas remarqué la checkbox. Sprint 10 aligne le default sur le workflow réel.
- **Surface chirurgicale** : 5 fichiers backend (model, API, factory, adapter, migration), 1 fichier frontend (`C2ConfigCard.tsx`), 1 ligne `SPEC.md`.
- **Helper text durci** : "Uncheck only for lab Mythic with self-signed certificates. **Disabling verification exposes the API token to MITM.**" — tradeoff sécu spell-out direct, opt-in conscient.
- **Migration `0008`** : `server_default` flippé à `sa.false()` via `op.batch_alter_table` (SQLite). Round-trip `upgrade → downgrade → upgrade` testé. Les lignes existantes **ne sont pas mutées** (valeur stockée préservée).
- **urllib3 spam suppression** : `urllib3.disable_warnings(InsecureRequestWarning)` gated sur `not verify_tls` dans `MythicAdapter.__init__`. Pas de suppression module-level.
### Fix 2 — `/graphql` → `/graphql/` (HTTP 301 fix)
Bug surface en user testing avec un Mythic réel à `https://192.168.10.60:7443` : `test_connection` retournait `HTTP 301`. Cause confirmée à 92 % par un workflow diagnostic (lecture adapter + WebFetch source canonique Mythic_Scripting), puis confirmée par l'utilisateur via `curl -kv` (Location header : `/graphql/`).
- Notre adapter sprint 8 a hardcodé `"/graphql"` sans slash final. Le client officiel `mythic_utilities.get_http_transport` utilise `f"{...}:{port}/graphql/"` (slash REQUIS). Nginx Mythic 3.x retourne `301` pour la canonicalisation.
- Le `allow_redirects=False` du sprint 8 (défense SSRF intentionnelle, **gardée**) faisait surface le 301 raw au lieu de le suivre.
- **Fix** : 1 caractère dans `mythic.py:119` + maj du commentaire de pinning + 3 fichiers de tests + 1 nouveau test de régression.
**Reviews** : code-review APPROVED + 1 NIT non-bloquant ; design-review APPROVED (0 finding) ; spec-review APPROVED post-amendement (`SPEC.md:85` ligne du default mise à jour).
## Test plan
- **Backend** : **469 / 469** pytest (a grandi de 1 — nouveau test de régression `test_mythic_adapter_url_has_trailing_slash`). `ruff` + `mypy --strict` clean.
- **Frontend** : **212 / 212** vitest. `tsc --noEmit` + `eslint --max-warnings=0` clean.
- **Migration round-trip** : `alembic upgrade head` → `downgrade -1` → `upgrade head` sur SQLite fresh, OK.
- **E2E** : non re-joué (mur de credentials Vite proxy persistant). Test manuel ci-dessous.
## Comment tester en local
```bash
# Avec la branche checkout et MIMIC_ENCRYPTION_KEY set :
export MIMIC_ENCRYPTION_KEY=$(python3 -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')
export MIMIC_C2_ADAPTER=fake # pour tester sans Mythic live
make build && make start
make create-admin USER=alice PASS=changeme8 # premier setup
# http://127.0.0.1:5000
```
Scénarios :
1. **Default vérifié à la création** — ouvre un engagement en édition → carte "C2 configuration" → la checkbox **"Verify TLS certificate" est décochée par défaut**, helper text MITM visible juste en-dessous.
2. **Test connection avec self-signed (cas dominant)** — colle l'URL d'un Mythic self-signed `https://<lab>:7443`, paste un token, **laisse la box décochée**, `Save` → `Test connection` → réponse `{ ok: true }` (FakeAdapter répond toujours OK, en prod-live le cert self-signed n'est plus rejeté). Tail backend stderr : **0 `InsecureRequestWarning`** (suppression engagée).
3. **Verify forced (cas explicite prod)** — recoche la box, `Save`, `Test connection` contre un cert self-signed → réponse `{ ok: false, error: "<SSLError CERTIFICATE_VERIFY_FAILED>" }`. Prouve que le flag est toujours honoré bout-en-bout.
4. **Migration** — sur une DB fresh : `alembic upgrade head` puis `pragma table_info(c2_config)` (SQLite) → `verify_tls` default `0`. Puis `INSERT INTO c2_config (engagement_id, url, api_token_encrypted) VALUES (1, 'https://x', 'y')` → la row aura `verify_tls = 0`.
5. **Rows existantes intouchées** — une row sauvegardée avec `verify_tls = 1` AVANT cette migration reste à `1`. L'opérateur qui veut basculer doit explicitement re-Save la config avec la box décochée.
6. **RBAC unchanged** — un user SOC reçoit toujours `403` sur `/api/engagements/<id>/c2-config*`. Aucun changement.
## Fichiers touchés
| Path | Change |
|---|---|
| `SPEC.md:85` | `verify_tls défaut true` → `défaut false` + justification |
| `backend/app/models/c2_config.py:22` | `default=True` → `default=False` |
| `backend/app/api/c2.py:87` | fallback `True` → `False` |
| `backend/app/services/c2/factory.py:9` | `verify_tls: bool = True` → `False` |
| `backend/app/services/c2/mythic.py:18,19,116,122-125` | `True` → `False` + `urllib3` import + `disable_warnings(InsecureRequestWarning)` gated sur `not verify_tls` |
| `backend/migrations/versions/0008_c2_verify_tls_default_false.py` | NEW — `server_default` flippé via `batch_alter_table` |
| `backend/tests/test_c2_config.py` | assertion `is False` (renamée `default_false`) ; tests qui PUT `verify_tls=True` explicitement laissés intacts |
| `frontend/src/components/C2ConfigCard.tsx` | `useState(false)` + delete-reset `false` + helper text MITM en `text-graphite` mirror FormField hint |
## Security tradeoff acknowledged
Default sécu-relevant flippé en pleine conscience :
- Mimic est un BAS / red-team lab tool dont le 99e percentile d'usage est Mythic self-signed.
- Toggle reste visible et opt-in, helper text spell-out le risque MITM directement.
- Decision lockée dans `tasks/todo.md` § Decisions + ce PR body.
- Plugin `security-guidance@claude-code-plugins` a flagged 3 fois (HIGH × frontend useState, HIGH × API fallback, MEDIUM × adapter/factory/model/migration). Conditions de "proceed without changes" du plugin elles-mêmes remplies : user a explicitement demandé le flip + tradeoff surfacé dans l'UI.
- Rows existantes `verify_tls=true` **non mutées** par la migration — defense in depth pour qui aurait déjà bouclé sa config en mode strict.
## NITs reportés (non-bloquants, polish sprint éventuel)
- `test_c2_config.py:209` — renamer `test_get_config_verify_tls_default_false` en `test_get_config_verify_tls_roundtrip_false` (le test PUT explicitement la valeur, ne teste pas le default ORM). Optionnel ; cosmétique.
- `mythic.py:122-125` — `urllib3.disable_warnings` est process-global. Alternative cleaner : `warnings.catch_warnings()` dans `_post()`. Plus de code pour bénéfice marginal (aucun autre caller urllib3 dans Mimic). Skipped.
- Symétrie `test_migration_0008_c2.py` absent vs 0006/0007 ont chacun le leur. 0008 est 10 lignes, round-trip déjà confirmé manuellement, faible risque. Skipped.
## Commits
```
c31f985 docs(sprint-10): plan flip verify_tls default to false (redteam-friendly)
774ae05 fix(frontend): default verify_tls checkbox to unchecked + add helper text
ee5fda6 fix(frontend): make verify_tls helper text spell out the MITM tradeoff
0fab40b feat(backend): default verify_tls to false for lab/redteam Mythic
91533c2 docs(spec): align c2_config.verify_tls default with sprint 10 (false)
208c6e6 fix(c2): append trailing slash to mythic /graphql URL to avoid 301 redirect
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Flip useState(true) → useState(false) and delete-reset from true → false
so a freshly-created C2 config does not block self-signed Mythic instances.
Add hint paragraph below the checkbox matching FormField hint style
(text-[12px] text-graphite): "Leave unchecked for lab Mythic with
self-signed certificates."
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
flip default to False at model, API fallback, adapter, and factory layers.
add migration 0008 flipping c2_config.verify_tls server_default to false.
suppress urllib3 InsecureRequestWarning when verify_tls is off.
adapt c2 tests to new verify_tls default.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Nginx fronting Mythic 3.x redirects /graphql → /graphql/ (HTTP 301).
The sprint 8 SSRF defense (allow_redirects=False) is correct and intentional;
the fix is the URL, not the redirect policy. Upstream reference:
mythic_utilities.get_http_transport hardcodes /graphql/ with trailing slash.
Updated _GQL_URL in all three adapter test files to match.
Added TestMythicAdapterUrlConstruction regression test to prevent silent
regression on future refactors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
knacky
merged commit 65857134ef into main2026-06-21 19:44:33 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Fix 1 —
verify_tlsdefaultMimic est un BAS lab tool dont le workflow dominant est un Mythic auto-hébergé avec certificat self-signed. L'ancien default
verify_tls=Trueà chaque couche cassait silencieusement la première connexion Test (CERTIFICATE_VERIFY_FAILED) tant que l'opérateur n'avait pas remarqué la checkbox. Sprint 10 aligne le default sur le workflow réel.C2ConfigCard.tsx), 1 ligneSPEC.md.0008:server_defaultflippé àsa.false()viaop.batch_alter_table(SQLite). Round-tripupgrade → downgrade → upgradetesté. Les lignes existantes ne sont pas mutées (valeur stockée préservée).urllib3.disable_warnings(InsecureRequestWarning)gated surnot verify_tlsdansMythicAdapter.__init__. Pas de suppression module-level.Fix 2 —
/graphql→/graphql/(HTTP 301 fix)Bug surface en user testing avec un Mythic réel à
https://192.168.10.60:7443:test_connectionretournaitHTTP 301. Cause confirmée à 92 % par un workflow diagnostic (lecture adapter + WebFetch source canonique Mythic_Scripting), puis confirmée par l'utilisateur viacurl -kv(Location header :/graphql/)."/graphql"sans slash final. Le client officielmythic_utilities.get_http_transportutilisef"{...}:{port}/graphql/"(slash REQUIS). Nginx Mythic 3.x retourne301pour la canonicalisation.allow_redirects=Falsedu sprint 8 (défense SSRF intentionnelle, gardée) faisait surface le 301 raw au lieu de le suivre.mythic.py:119+ maj du commentaire de pinning + 3 fichiers de tests + 1 nouveau test de régression.Reviews : code-review APPROVED + 1 NIT non-bloquant ; design-review APPROVED (0 finding) ; spec-review APPROVED post-amendement (
SPEC.md:85ligne du default mise à jour).Test plan
test_mythic_adapter_url_has_trailing_slash).ruff+mypy --strictclean.tsc --noEmit+eslint --max-warnings=0clean.alembic upgrade head→downgrade -1→upgrade headsur SQLite fresh, OK.Comment tester en local
Scénarios :
https://<lab>:7443, paste un token, laisse la box décochée,Save→Test connection→ réponse{ ok: true }(FakeAdapter répond toujours OK, en prod-live le cert self-signed n'est plus rejeté). Tail backend stderr : 0InsecureRequestWarning(suppression engagée).Save,Test connectioncontre un cert self-signed → réponse{ ok: false, error: "<SSLError CERTIFICATE_VERIFY_FAILED>" }. Prouve que le flag est toujours honoré bout-en-bout.alembic upgrade headpuispragma table_info(c2_config)(SQLite) →verify_tlsdefault0. PuisINSERT INTO c2_config (engagement_id, url, api_token_encrypted) VALUES (1, 'https://x', 'y')→ la row auraverify_tls = 0.verify_tls = 1AVANT cette migration reste à1. L'opérateur qui veut basculer doit explicitement re-Save la config avec la box décochée.403sur/api/engagements/<id>/c2-config*. Aucun changement.Fichiers touchés
SPEC.md:85verify_tls défaut true→défaut false+ justificationbackend/app/models/c2_config.py:22default=True→default=Falsebackend/app/api/c2.py:87True→Falsebackend/app/services/c2/factory.py:9verify_tls: bool = True→Falsebackend/app/services/c2/mythic.py:18,19,116,122-125True→False+urllib3import +disable_warnings(InsecureRequestWarning)gated surnot verify_tlsbackend/migrations/versions/0008_c2_verify_tls_default_false.pyserver_defaultflippé viabatch_alter_tablebackend/tests/test_c2_config.pyis False(renaméedefault_false) ; tests qui PUTverify_tls=Trueexplicitement laissés intactsfrontend/src/components/C2ConfigCard.tsxuseState(false)+ delete-resetfalse+ helper text MITM entext-graphitemirror FormField hintSecurity tradeoff acknowledged
Default sécu-relevant flippé en pleine conscience :
tasks/todo.md§ Decisions + ce PR body.security-guidance@claude-code-pluginsa flagged 3 fois (HIGH × frontend useState, HIGH × API fallback, MEDIUM × adapter/factory/model/migration). Conditions de "proceed without changes" du plugin elles-mêmes remplies : user a explicitement demandé le flip + tradeoff surfacé dans l'UI.verify_tls=truenon mutées par la migration — defense in depth pour qui aurait déjà bouclé sa config en mode strict.NITs reportés (non-bloquants, polish sprint éventuel)
test_c2_config.py:209— renamertest_get_config_verify_tls_default_falseentest_get_config_verify_tls_roundtrip_false(le test PUT explicitement la valeur, ne teste pas le default ORM). Optionnel ; cosmétique.mythic.py:122-125—urllib3.disable_warningsest process-global. Alternative cleaner :warnings.catch_warnings()dans_post(). Plus de code pour bénéfice marginal (aucun autre caller urllib3 dans Mimic). Skipped.test_migration_0008_c2.pyabsent vs 0006/0007 ont chacun le leur. 0008 est 10 lignes, round-trip déjà confirmé manuellement, faible risque. Skipped.Commits
🤖 Generated with Claude Code