Audit staff-level de pré-production du fork OpenCode. Périmètre : monorepo Bun (
packages/*), crates Rust (crates/*), app mobile Android (packages/mobile), sidecar CLI (packages/opencode). Méthode : lecture directe + recoupement des audits existants (AUDIT_REPORT.md, SECURITY_AUDIT.md, ANDROID_AUDIT.md, PERFORMANCE_REPORT.md, KNOWN_ISSUES.md). Les findings déjà listés sont référencés (cf. AUDIT_REPORT.md A.x), les findings nouveaux détectés par cette passe sont préfixésP*. Aucunbun audit/cargo audit/gitleaksn'a été exécuté (non disponibles dans l'environnement) — les points de vérification sont explicitement listés dans la checklist.
Le fork est architecturalement mûr (auto-config llama.cpp, DAG d'agents, worktrees isolés, 9 états de tâches, scanner vulns, prompt caching Anthropic). Les 4 passes d'audit précédentes ont fermé la majorité des findings critiques (CSP, cost underflow, devtools, tokenizer, reasoning budget, lifecycle Android). Cependant plusieurs blockers ouverts empêchent un merge dev → main immédiat : auth.json plaintext, WebSocket auth via query-string visible dans les logs, cleartext global sur Android, absence de signature/SBOM des artefacts, aucun scan dépendance automatisé, absence de budget cost-cap côté utilisateur (task REST POST /:id/followup sans rate-limit ni quota).
Note : 72 / 100 Verdict : GO CONDITIONNEL — merge possible sous réserve de traiter les BLOCKERS B1–B6 ci-dessous. Les warnings W1–W9 peuvent suivre dans les deux sprints post-merge.
- Sévérité : critique
- Fichier :
packages/opencode/src/auth/index.ts(cf. SECURITY_AUDIT S1.S2) - Impact utilisateur : une sauvegarde complète du
$HOME(Time Machine, Backblaze,adb backupcôté Android) exfiltre les tokens Anthropic/OpenAI/Copilot. Mode 0o600 ne protège que les autres utilisateurs locaux. - Remédiation : migration keychain OS (
keytarcôté desktop,EncryptedSharedPreferencesvia plugin Tauri côté Android). Au minimum : chiffrement AES-GCM avec clé dérivée DPAPI / Keychain / libsecret. - Effort : L
- Sévérité : critique
- Fichier :
packages/opencode/src/server/auth-jwt.ts:110-145(cf. SECURITY_AUDIT S1.S1) - Impact utilisateur : credentials Basic-auth visibles dans
logcat, proxies d'entreprise, access logs nginx. En pairing LAN sur Wi-Fi partagé, un sniffeur passif récupère la session. - Remédiation : header custom via Tauri command native (tauri-plugin-http + tungstenite) ; en desktop, handshake cookie one-shot avant upgrade. Limiter le WS à
127.0.0.1pour le desktop (déjà partiellement le cas). - Effort : M
- Sévérité : critique
- Fichier :
packages/mobile/src-tauri/gen/android/app/src/main/res/xml/network_security_config.xml(cf. SECURITY_AUDIT S2.S2) - Impact utilisateur : combiné avec B2, un attaquant sur le même Wi-Fi peut intercepter les credentials en HTTP non TLS. Scope global au lieu du LAN RFC1918 uniquement.
- Remédiation :
<base-config cleartextTrafficPermitted="false" />+<domain-config cleartextTrafficPermitted="true">uniquement pour10.0.0.0/8,172.16.0.0/12,192.168.0.0/16. QA sur device physique requis. - Effort : S
- Sévérité : haute (blocker car impacte fiabilité en production)
- Fichier :
packages/opencode/src/mcp/oauth-callback.ts,packages/opencode/src/local-models/ollama.ts(cf. SECURITY_AUDIT S1.V2) - Impact utilisateur : un IdP/serveur Ollama lent bloque indéfiniment l'Effect scope, fuite de file descriptors et abonnés SSE. DoS trivial depuis un serveur malicieux déclaré par l'utilisateur.
- Remédiation : wrapper
AbortSignal.timeout(15000)(pattern déjà utilisé danswebfetch.ts/websearch.ts). - Effort : S
- Sévérité : critique
- Fichier :
packages/opencode/src/file/index.ts:305-665(cf. SECURITY_AUDIT S1.V3) - Impact utilisateur : exfiltration arbitraire.
Instance.containsPathempêche..mais un symlinkproject/docs -> /etc/shadowcontourne le guard. Un repo Git malicieux (dep npm, git clone) plante un symlink, l'agent est incité àread("docs/..."). - Remédiation :
fs.lstat(resolved)+ rejet siisSymbolicLink()et la cible résolue n'est pas sousInstance.directory. - Effort : M
- Sévérité : critique (supply chain)
- Fichier :
.github/workflows/*.yml— aucundependabot.yml, aucuncodeql.yml, aucuntrivy/grype, aucuncosign/sigstore(vérifié : 33 workflows, aucun ne matchedependa|codeql|trivy|sbom|cosign|sign) - Impact utilisateur : aucun signalement de CVE critique dans
bun.lock/Cargo.lock. APKs / binaires desktop non signés cryptographiquement → impossible pour un utilisateur de vérifier la provenance. - Remédiation :
- Ajouter
.github/dependabot.yml(npm/Bun + cargo + github-actions). - Activer GitHub secret scanning + push protection côté repo settings.
- Workflow
codeql.yml(JS/TS + Rust viacodeql-action). - Générer SBOM SPDX/CycloneDX via
anchore/sbom-actionsur release. - Signer APK/dmg/msi via
sigstore/cosignou GitHub attestationactions/attest-build-provenance.
- Ajouter
- Effort : M
- Sévérité : haute
- Fichier :
packages/opencode/src/server/routes/task.ts:318-399 - Impact : si le serveur REST est exposé au-delà de
127.0.0.1(mode LAN pairing), un client authentifié peut enchaînerfollowupsans plafond token/coût → facture cloud explosive, local-llm saturé. Le handlerawait SessionStatus.set(id, { type: "busy" })est la seule barrière, pas de quota cumulé. - Remédiation : budget cumulé par session (token + USD), refus 429 au-delà. Expose la limite via
GET /task/:id(costCap,costUsed). - Effort : M
- Sévérité : moyenne
- Fichier :
packages/opencode/src/server/routes/task.ts:20-27 - Impact : un bug Workspace (DB corrompue, permission) renvoie
undefinedsilencieusement → l'UI affiche "pas de worktree" au lieu d'une erreur traçable. Plusas anysurworkspaceIDligne 21. - Remédiation : log warn, typage strict
WorkspaceID. - Effort : S
- Sévérité : moyenne
- Fichier :
packages/opencode/src/server/routes/task.ts:505 - Impact : si le schéma
MessageV2.Assistantajoute/supprimecost, silence pendant le typecheck — l'UI peut faire passer0pour un vrai coût. - Remédiation : extraire dans un helper typé
getCost(msg)avec narrowing. - Effort : S
- Sévérité : haute (perf)
- Fichier :
packages/opencode/src/local-llm-server/index.ts:464-489(buildArgs) - Impact : aucun
--slots/--cache-reuse/--n-predict,--mmapnon explicitement activé (défaut llama.cpp), pas de--draft-model/ speculative decoding. Sur Qwen3-Coder 32B + drafter 0.6B, perte de 40–60% de tokens/s et zéro réutilisation de préfixe entre tours → chaque message relance la prefill complète. Impact utilisateur direct : latence perçue 2–3×. - Remédiation :
- Ajouter
--slot-save-path+--cache-reuse 256+--mmapexplicite. - Exposer
OPENCODE_DRAFT_MODELpour speculative decoding ; auto-probe si un drafter*-0.5B-*.ggufest présent à côté du modèle principal. - Persister le KV cache du tour précédent (
--prompt-cache).
- Ajouter
- Effort : L
- Sévérité : haute
- Fichier :
packages/opencode/src/local-llm-server/index.ts:508-554(cf. AUDIT_REPORT A.8, toujours ouvert) - Impact : VRAM churn + batterie Android si mismatch de nom.
- Remédiation : compteur module-level, fail-hard après 3 restarts/2 min.
- Effort : S
- Sévérité : haute
- Fichier :
packages/opencode/src/tool/task.ts:159-292 - Impact : un agent orchestrator peut lancer N tâches
mode: "background"en parallèle → N worktrees Git créés (disque), N prompts llama-server qui se battent pour la VRAM, N connexions SSE. Pas de sémaphore, pas de queue. - Remédiation : semaphore par projet (max 4 par défaut), queue FIFO ; les tâches en excès passent en
queuedau lieu debusy. Config :experimental.task.max_parallel. - Effort : M
- Sévérité : moyenne
- Fichier :
packages/opencode/src/mcp/index.ts:681-687 - Impact : deux serveurs MCP
fooetfoo_bar— une toolfoo_bar_listest attribuée aux deux si scoping strict. Préfixe non suffixé par séparateur unique. - Remédiation : séparer name + tool via un délimiteur non-ambigu (
::ou un Map name→toolKeys maintenue aulistTools). - Effort : S
- Sévérité : moyenne
- Fichier :
packages/opencode/src/server/server.ts:64-88(cf. SECURITY_AUDIT S2.A1) - Impact : sous-domaine de preview compromis → CSRF le serveur local.
- Remédiation : allowlist explicite.
- Effort : S
- Sévérité : moyenne
- Fichier :
packages/desktop/src-tauri/src/cli.rs:371-480(cf. SECURITY_AUDIT S2.S1) - Remédiation : allowlist
SHELL_ENV_KEYS. - Effort : S
- I1 — Observabilité : pas de crash reporter opt-in (Sentry/Bugsnag absent). Les stack traces llama-server sont tronquées 4096 B (cf. AUDIT_REPORT A.17). Ajouter un crash reporter local-first (log rotatif) + opt-in upload.
- I2 — Feature flags :
cfg.experimental?.*utilisé mais pas de système centralisé (toggle runtime, kill switch). Introduire un moduleflags/consommé par les call sites. - I3 — Export / suppression RGPD : aucun endpoint
DELETE /user/datani export JSON. Pour un mode "entreprise", obligatoire. - I4 — Audit logs multi-tenant : les events
SessionStatus.Event.*sont publiés sur le bus mais non persistés pour audit. Ajouteraudit_logDrizzle table. - I5 — BYOK : actuellement les API keys sont en
auth.jsonet inhérentes au user. Un mode BYOK par projet (override.opencode/byok.jsonchiffré) manque pour un usage entreprise. - I6 — Batch API : pas d'utilisation de
messages/batchesAnthropic ni OpenAI batch. Pour les tâchesbackgroundnon urgentes, économie 50%. - I7 — Scanner vulnérabilités intégré (
security/scanner.ts) : bonnes règles de base, maissql-injectionpattern ne détecte que${...}ou+ user— manque%{user}, format-strings Python, heredocs.- Pas de scan de
tool.outputavant affichage côté UI (prompt injection → exfil de secrets). - Pas de détection Slack/Stripe/Datadog/GitHub token modernes (regex limités à AWS/JWT/generic).
- I8 —
auto-config.tsne détecte pas le NPU (Qualcomm Hexagon, Apple Neural Engine, Intel NPU). Pour mobile Snapdragon 8 Gen 3, une delegation NPU (via ExecuTorch / QNN) gagnerait 3–5× tokens/s vs GPU Adreno. - I9 — Thermal listener Android JNI documenté comme "deferred mobile work" (KNOWN_ISSUES). À prioriser pour une release publique.
- I10 — Cascading models : un helper "si modèle local échoue → cloud fallback" absent. Seul
deriveConfigthrow si pas de GPU ; pas de chemin de dégradation. - I11 — Tests : aucun test E2E qui couvre le DAG d'agents (orchestrator → explore → critic), seulement des unit tests par tool. Écrire un E2E fixture "debug + propose patch + review".
- I12 — Dupliquer
auth-jwtcôté mobile vs desktop — pas de test d'intégration qui vérifie qu'une session survit à un kill + restart sidecar (reprise 9 états).
- Gestion d'erreur : 70 occurrences de
as anydanspackages/opencode/src/(vérifié par grep), dont 10 danssession/index.tsxTUI et 3 danssession/prompt.ts. Un seulcatch {}strictement vide (global/index.ts). Les erreurs de task REST (/resume,/followup) ont un double catch imbriqué pour être sûres de publierTaskFailed— cf.task.ts:294-313— ce qui est bien, mais rend le code dense. - Tests : pipeline
unit + e2e (Playwright)sur linux/windows (.github/workflows/test.yml). Pas de matrice Android/iOS. Pas de bench régression (cf. PERFORMANCE_REPORT §3). - Reprise après crash : 9 états persistés (
session/status.ts:29-36) — bien.SessionStatus.setécrit en DB avant d'updater l'in-memory → OK si DB write fail (état in-memory restera cohérent).Database.usedanspersistToDb/readFromDbprotégé partry/catchsilencieux (status.ts:173,192) — acceptable car best-effort documenté. - Worktree lifecycle (
worktree/index.ts) :removeva jusqu'àbranch -D,resetfaitsubmodule update --recursive+clean -ffdx. Manque :git worktree prune --verboseappelé au démarrage pour nettoyer les worktrees orphelins (après crash desktop). - Task DAG :
TaskToolcrée session enfant, PublishTaskCreated/Completed/Failed. Bug potentiel → W6 (pas de limite concurrence).
- Cold start : cf. PERFORMANCE_REPORT §1 (HP1 = +400 ms CLI, +1-2 s mobile dû à 20+ SDKs importés statiquement). Non fixé.
- llama.cpp config : W4 ci-dessus, trou significatif (pas de prompt caching local, pas de speculative decoding, pas de slot save).
- Scalabilité backend : serveur Hono + Bun. Pas de pool DB visible (SQLite Drizzle, single-writer → OK mais limite concurrence).
- Mobile énergie : KNOWN_ISSUES mentionne thermal listener deferred. Pas de warning batterie <40%.
- Scanner intégré (
security/scanner.ts) : règles pertinentes mais limitées (cf. I7). Exécution manuelle, pas de CI intégrée (à lier à B6 CodeQL). bun audit/cargo auditnon exécuté dans cet audit (outil non disponible) — le lockfile comporte des milliers d'entrées, je ne peux pas certifier l'absence de CVE critique sans run outillé.- Secrets : pas de match positif sur
AKIA|-----BEGIN|sk-[A-Za-z0-9]{20}danspackages/. Seuls les regex du scanner + test fixtures (test/security/scanner.test.ts) matchent — pas de secret committé détecté. Confirmation gitleaks toutefois recommandée. - CSP : desktop + mobile désormais strictes (cf. KNOWN_ISSUES, commit A.10).
- Binds réseau : sidecar binding
127.0.0.1→packages/opencode/src/local-llm-server/index.ts:469. OK. - Signature : aucune (B6).
- Collecte : telemetry OpenTelemetry uniquement si
experimental.openTelemetry === true(config.ts:1031) — opt-in, bon. - Chiffrement at-rest : absent (B1).
auth.json0o600. - Chiffrement in-transit : B2 + B3.
- Local-first : ✅ sauf pairing LAN (cleartext par défaut, B3).
- RGPD : pas d'endpoint export/delete (I3).
- Multi-tenant : mono-user, pas de namespace ; audit logs absents (I4).
- BYOM : ✅ via
auth.json+config.provider. - Portabilité : ✅ CLI standalone + SDK TS, Python, Go.
- Transparence : ✅ modèle + provider affichés dans la status line TUI. Tools visibles (permission ruleset).
- Offline-first : ✅ llama-server embarqué.
- Politique contenu : aucune censure visible (pas de liste de refus serveur-side).
- Détection RAM / CPU big.LITTLE / GPU backend (CUDA/ROCm/Vulkan/Metal) : ✅ bien implémenté (
auto-config.ts:42-110). - Décision n_gpu_layers selon VRAM : ✅
(vramBudget / mbPerLayer)ligne 142. - Thermal : champ
thermalStateexiste mais n'est jamais mis à jour dynamiquement (le probe retourne toujours"nominal"ligne 107). LethermalMultligne 121 n'agit donc jamais. Cf. KNOWN_ISSUES "Thermal listener JNI" deferred. - KV cache quant adaptatif : ✅ f16/q8_0/q4_0 ligne 155.
- Pas de détection NPU (I8).
OPENCODE_ALLOW_CPU_ONLY=1obligatoire si pas de GPU : ✅ cohérent avec la règle "Never CPU-only" (memory).
mmap: implicite (défaut llama.cpp), pas d'opt-in explicite dansbuildArgs.- Prompt caching : ❌ absent (W4).
- KV cache persistant : ❌ absent (W4).
- Speculative decoding : ❌ pas de
--draft-model(W4). - Context compression : ✅
session/compaction.tsavec seuils scalés aumodel.limit.context. - Token budget :
Token.estimatecorrigé (cf. KNOWN_ISSUES A.1).
- Prompt caching Anthropic ephemeral : ✅
provider/transform.ts:250-262posecacheControl: { type: "ephemeral" }+cache_control+copilot_cache_control. - Streaming SSE : ✅
Stream.fromAsyncIterablecôté server ; SSE heartbeat avec double-stop race mineure (SECURITY_AUDIT S2.L1). - Batch API : ❌ I6.
- Cascading (cloud ↔ local) : ❌ I10.
- Abstraction provider : ✅ via
aiSDK +transform.ts.
- Télémétrie : opt-in OTel uniquement.
- Logs :
Log.create({ service: ... })standard ; pas de PII détectée dans les tags couverts. - Crash reporting : ❌ I1.
- Feature flags : partiellement via
experimentaldans config (I2). - Rollback / compat stockage : Drizzle migrations présentes (
packages/opencode/src/session/session.sql.ts). Pas de stratégie de rollback visible pour schémas down-migration.
- 33 workflows, dont
test.yml,typecheck.yml,android.yml,publish.yml. Correct. - Absent :
dependabot.yml,codeql.yml,trivy/grype,cosign, SBOM. → B6. - Pas de branch protection visible dans le repo (à confirmer en settings GitHub).
.github/actions/setup-buncustom action, à auditer (non lu).- Pas de reproductibilité documentée (build hashes).
-
bun turbo test:civert sur linux + windows -
bunx playwright testvert (packages/app) -
bun typecheckdans chaquepackages/*concerné -
cargo check --releasedanspackages/*/src-tauri+crates/* -
cargo clippy -- -D warningspropre
-
bun auditsans high/critical -
cargo auditsans high/critical -
gitleaks detect --redact→ 0 finding -
codeql-action/analyze(JS/TS + Rust) sans error
- B1..B5 fixés et testés
- B3 testé sur device Android physique (pairing LAN HTTP → doit refuser)
- Reprise session post-kill : état 9 correctement restauré
- Lancer 10 tâches background → le semaphore W6 limite à N parallèles
- Cold start mobile <6 s (milieu de gamme)
- Heap snapshot WebView stable après 100 messages + 10 abort/retype (valide ou invalide AUDIT_REPORT A.3)
- Signature APK + attestation build-provenance présentes sur release candidate
- B3 (S) —
network_security_config.xmlLAN-only cleartext. QA device physique. - B4 (S) —
AbortSignal.timeout(15000)sur les 4 fetch identifiés. - B5 (M) —
lstat + resolveanti-symlink dansfile/index.ts. - B6 (M) —
dependabot.yml+codeql.yml+ secret scanning + SBOM job de release + attestation build-provenance. - B2 (M) — WS auth via cookie one-shot (desktop) + header natif Tauri (mobile).
- W5 (S), W7 (S), W8 (S), W9 (S) — quick wins incorporables dans le même sprint.
- B1 (L) — migration keychain OS.
- W1 (M) — cost-cap + rate-limit REST tasks.
- W6 (M) — semaphore background tasks.
- W4 partiel (M) — prompt-cache + mmap explicite ;
--draft-modelvia env (I8 reste backlog). - W2/W3 (S/S) — typing
task.ts.
- I1 — crash reporter local-first + opt-in upload.
- I3, I4, I5 — export/delete RGPD + audit_log table + BYOK projet.
- I10 — cascading cloud/local automatique.
- I7 — scanner : élargir les regex tokens modernes, scanner les tool outputs avant render.
- I9 — thermal listener JNI Android branché à
resetProfileCache(). - I11 — E2E fixture DAG agents.
Les findings suivants, listés dans les audits antérieurs, restent ouverts et sont couverts ci-dessus :
| Origine | ID | Statut |
|---|---|---|
| AUDIT_REPORT | A.3 stream cleanup | 🔎 à mesurer (heap snapshot) |
| AUDIT_REPORT | A.5 runtime permissions Android | ouvert |
| AUDIT_REPORT | A.6 Promise.all sans abort |
ouvert |
| AUDIT_REPORT | A.8 ensureCorrectModel restart loop | W5 |
| AUDIT_REPORT | A.9 secrets localStorage mobile | ouvert (lié B1) |
| AUDIT_REPORT | A.12 _ownedChildPid stale |
ouvert |
| AUDIT_REPORT | A.17 stderr 4096 B | ouvert (→ I1) |
| SECURITY_AUDIT | S1.L2 markdown cache 200 | ouvert |
| SECURITY_AUDIT | S1.L3 session-prefetch cache | ouvert |
| SECURITY_AUDIT | S2.A2 deep-link providerID | ouvert |
| SECURITY_AUDIT | S2.V1 RPC worker ID reuse | ouvert |
| SECURITY_AUDIT | S2.V2 embedding response non validée | ouvert |
| KNOWN_ISSUES | Vim/altscreen terminal, mouse tracking, virtual keybinding row, thermal listener, neural voice clone | deferred |
Auditeur : Claude Opus 4.7 (1M) — 2026-04-18 Contact : barat.erwan@gmail.com