Scopo
Diagramma e descrizione dei flussi del dato dentro Hermes Console: dove entra il codice del cliente, dove transita, dove viene memorizzato, dove va in uscita, quando viene cancellato. Documento richiesto dai team procurement/DPO ai fini GDPR Art. 30, NIS2 Art. 21(2)(d), e da ISO27001 Annex A.5.34.
Confini di trust
┌──────────────────────────────────────────────────────────────┐
│ ZONA "CLIENTE" (sotto controllo del cliente, fuori OASI) │
│ - repo Git (GitHub / GitLab / Bitbucket / on-prem / local) │
│ - bundle codice locale (.zip / .tar.gz) ← Sprint 2 §B │
│ - dispositivi operatori cliente (laptop, browser) │
└───────┬──────────────────────────┬──────────┬──────────────┘
│ HTTPS git clone+push │ HTTPS │ TLS (UI cookie)
│ (SSH deploy key) │ upload │
│ │ multipart│
│ ▼ ▼
┌───────▼────────────────────────────────────────────────────┐
│ ZONA "OASI HERMES" (perimetro OASI S.r.l.) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ nginx (TLS) │→ │ hermes-console│→ │ Postgres (dedic.)│ │
│ │ HSTS │ │ Next 16 + API │ │ schema hermes/ │ │
│ └──────────────┘ └─────┬─────────┘ │ hermes_app │ │
│ │ └──────────────────┘ │
│ ┌────────────────┼─────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ tmp dir │ │ ChildProcess │ │ Redis (rate │ │
│ │ /tmp/hermes-X│ │ git/osv-scan│ │ limit cache)│ │
│ │ deleted in │ │ ssh-keygen │ └──────────────┘ │
│ │ finally{} + │ │ unzip / tar │ │
│ │ orphan sweep │ └──────────────┘ │
│ │ >60min │ │
│ └──────────────┘ │
└────────┬──────────────────┬──────────────────────────────────┘
│ HTTPS │ HTTPS
│ API call │ webhook (Stripe → noi)
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ ANTHROPIC │ │ STRIPE (opz.) │
│ api.anthropic │ │ subscription evt │
│ Claude opus-4-7 │ │ → tenant.plan │
│ chiave OASI o │ │ (no codice qui) │
│ chiave TENANT* │ │ │
│ * Sprint 2 §A │ │ │
└──────────────────┘ └──────────────────┘
Flussi del codice cliente — dettaglio
Flusso 1: Discovery via SSH clone (repo registrato)
- Operatore (browser → cookie WebAuthn) clicca "Avvia Discovery" su
/discovery. - POST
/api/hermes/discovery→requireAdmin+requirePaidse autopatch. - Server-side: decifra
deployKeyEnc(AES-256-GCM); scrive su file 0600 inmkdtemp(/tmp/hermes-discovery-XXX). git clone --depth 1viaGIT_SSH_COMMANDcon la deploy key → temp dir.- Cancellazione chiave: il file 0600 viene cancellato dopo il clone (
withDeployKeyfinally). StackDetector+LocCounter+VulnScanner(vedi flusso 2 per dettaglio LLM) operano sul temp dir.mkdtemp→rm -rfinfinally{}dirunDiscovery. Garantito anche su errore.- Persisitto in DB:
HermesDiscovery.report(StackReport JSON, no codice),.vulnReport(CVE JSON),.comprehension(LLM summary + risks),.remediation(proposals JSON),.locCount(int).
Dato sensibile in DB: il .comprehension.notable_risks e .remediation.proposals[].snippet possono contenere estratti del codice cliente (≤200 char per snippet, troncato). Il codice sorgente integrale NON è persistito.
Flusso 2: chiamata LLM (CodeComprehender + RemediationPlanner)
- Dopo il clone/estrazione (flusso 1 / 4 / 7), Hermes seleziona deterministicamente i top-N file source (default ≤14 file, ≤80 KB tot).
- Selezione chiave Anthropic (Sprint 2 §A):
- Se il tenant ha
anthropicKeyEncconfigurata → decifrata in-process e usata come Bearer. Chiamata esce dal contratto Anthropic del cliente. - Altrimenti →
env.ANTHROPIC_API_KEY(chiave globale OASI). Chiamata esce dal contratto Anthropic di OASI (Anthropic = subprocessor OASI ai sensi GDPR Art. 28).
- Se il tenant ha
- Payload inviato ad Anthropic via HTTPS:
- System prompt (italiano, hard-coded in repo).
- User content: stack rilevato + corpus file source del cliente.
- Risposta JSON parsed defensivamente.
- Nessuna persistenza presso Anthropic del prompt (default API tier — opt-out from training; ZDR se il cliente lo ha sottoscritto col proprio contratto).
- Output salvato in
HermesDiscovery.comprehension/.remediation.
Cosa esce dal perimetro OASI verso Anthropic:
- Nome del repo (label).
- Stack rilevato + framework hints.
- Lista
notable_risks(per RemediationPlanner). - Contenuto dei top-N file source selezionati (NON l'intero repo).
Cosa NON esce:
- Segreti rilevati nei file (vengono inviati così come sono nel codice originale — se il cliente ha SMTP password in chiaro, quella stringa È nel file ed è visibile al modello; Hermes non maschera né hashifica prima dell'invio — vedi Privacy notice LLM per il rationale e l'opzione opt-out per audit pre-redaction).
- Commit history, blame, tag, altri branch.
- File binari, lockfile, vendored deps (esclusi by-design dalla selezione).
Flusso 3: Apply→PR (autopatch via VCS provider)
Sprint 3 generalizza il flusso a 4 provider VCS: GitHub (Octokit), GitLab (REST v4, SaaS + self-host), Bitbucket Cloud (REST v2), Azure DevOps (REST 7.1). La scelta del provider è automatica in base al HermesRepo.host (vedi engine detectVcsProvider).
- POST
/api/hermes/discovery/[id]/apply→requirePaid + requireAdmin. - Decifra
prTokenEnc(token VCS, AES-256-GCM). Format del token per provider:- GitHub: fine-grained PAT (scope Contents+PR RW)
- GitLab: Personal Access Token (scope api+write_repository)
- Bitbucket:
username:app_password(scope Repositories+PR write) - Azure DevOps: PAT (scope Code RW + Pull Request RW)
- Clone HTTPS con URL provider-specific (
oauth2:per GitHub/GitLab,x-token-auth:per Bitbucket,hermes:<pat>@per Azure DevOps — vedi enginebuildAuthenticatedCloneUrl). InstrumentationApplierscrive i file telemetria +docs/hermes/discovery-report.mdnel working tree.git commitcon identity OASI + push branch +createPullRequestvia adapter scelto (createVcsClient(provider, token)).- Audit
discovery.applycontenantId+pr_url+provider. Temp dir cancellato infinally{}.
Verso il VCS provider: contenuto del nuovo branch (= scrittura intenzionale). Nessun altro dato.
Bitbucket nota: la sua API non supporta labels su PR — addLabels è un no-op silenzioso (la categorizzazione resta nel titolo [hermes] ... e nel branch name).
Azure DevOps nota: l'owner del repo registrato deve contenere {org}/{project} (Azure ha 3 livelli organizzativi: organization → project → repository).
Flusso 4: scarica .patch (autopatch sovrano)
- POST
/api/hermes/discovery/[id]/patch→requirePaid + requireAdmin. - Clone via deploy key SSH (no PAT GitHub).
InstrumentationApplier(mode:"patch")scrive i file + eseguegit add -N . && git diff HEAD.- Restituisce il
.patchcome responsetext/x-patchdirettamente al browser dell'operatore. - Nessun push, nessuna PR, nessun outbound verso GitHub.
- Temp dir cancellato.
HermesDiscoveryaggiornato con auditdiscovery.patch.
Out-of-OASI: il .patch torna SOLO all'operatore via TLS, mai a un terzo.
Flusso 5: download Report (PDF / Markdown)
- GET
/api/hermes/discovery/[id]/report(opzioni?format=md,?download=1). requireUser + canAccessTenant(404 se altro tenant).- Rendering server-side da
HermesDiscovery.report+vulnReport+comprehension+remediation— già in DB. - PDF generato vettorialmente con pdfkit (in-memory, no temp file).
- Output via TLS all'operatore.
Nessuna chiamata esterna in questo flusso.
Flusso 6: Stripe webhook (billing)
- Stripe invia POST
/api/billing/webhookcon headerStripe-Signature. - Verifica HMAC-SHA256 + tolleranza 5min + timing-safe.
- Lettura
event.data.object.metadata.tenantId→ aggiornamentoHermesTenant.plan. - Nessun codice cliente coinvolto in questo flusso. Solo metadata di billing.
- Audit
billing.webhookcon planFrom/planTo.
Flusso 7: Discovery via upload zip/tar.gz (Sprint 2 §B — sovranità intermedia)
Scenario: cliente con codice on-prem (folder Windows, repo mai pubblicato) che non può/vuole esporre un git remote a Hermes.
- Operatore (browser autenticato WebAuthn) seleziona
/discovery→ tab "Carica zip" → file picker. - POST multipart
/api/hermes/discovery/uploadcon campofile(.zipo.tar.gz, max 100 MB) →requireAdmin. - Server-side:
mkdtemp(/tmp/hermes-upload-XXX), scrittura bundle,unzip/tar -xzfcon--no-same-owner --no-same-permissionsin subdirectoryextracted/. - Validazione:
- Filename sanificato (no
/,\\, NUL byte, no traversal). - Estensione whitelistata (
.zip,.tar.gz,.tgz). - Size cap 100 MB (configurable via
UPLOAD_MAX_BYTES). - Entries cap 200000 file post-estrazione.
- Filename sanificato (no
runDiscovery(extractDir)→ identica pipeline di flusso 1+2 (Stack, Vuln, LOC gate FREE, LLM, Remediation). Vale anche il flusso 2.2: se il tenant ha la sua chiave Anthropic la usa.- Cancellazione:
rm -rf /tmp/hermes-upload-XXXinfinally{}della route +maxDuration=300come hard cap. - Orphan sweep: l'entrypoint del container esegue ad ogni boot
find /tmp -maxdepth 1 -name 'hermes-upload-*' -type d -mmin +60 -exec rm -rf {} +per coprire request abortite/crash. HermesDiscovery.repo = "upload:<filename>"(no URL git), tenant = quello dell'operatore. Auditdiscovery.uploadconbytes+kind+primaryStack+durationMs(no contenuto).
Cosa NON esce verso git provider: niente. Il bundle non viene mai
push-ato — è puramente input. Solo se l'operatore poi avvia Apply→PR
(flusso 3) con un repoId registrato il codice esce verso GitHub.
Flusso 8: error reporting & slow-query telemetry (Sprint 16, opt-in)
Scenario: il container Console emette un'eccezione (HTTP 5xx, route handler crash, ecc.) o una query Prisma supera la soglia di latenza configurata (default 1000 ms). Per facilitare il diagnosing senza guardare i log grezzi, gli eventi vanno a un'istanza GlitchTip self-host (compatibile con il protocollo Sentry) interna al perimetro OASI.
- SDK
@sentry/nextjsconfigurato in Console leggeSENTRY_DSNda env. Se l'env è assente → SDK in modalità no-op (nessun outbound). - Ogni
captureException/captureMessagepassa per il hookbeforeSenddisrc/lib/sentry-scrub.tsche:- Rimuove
Authorization,Cookie,X-API-Keydai request headers. - Sostituisce con
[redacted]ogni proprietà con key inSENSITIVE_KEYS(password, token, recoveryCode, githubToken, anthropicKey, ...) ricorsivamente nel body/extra/contexts. - Mantiene solo
user.id(chiave tecnica), elimina email/username/ip_address.
- Rimuove
- Eventi vanno in HTTPS a
https://errors.oasi.systems→ stack GlitchTip in/opt/oasi-glitchtip/sullo stesso box srvai01 (perimetro fisico OASI). - Slow queries: ogni operazione Prisma è cronometrata da
src/lib/db.ts(extension$allOperations). Se durata >SLOW_QUERY_THRESHOLD_MS(default 1000) →captureMessagelevel:warning con{model, operation, durationMs}(NIENTE args per evitare leak di PII / query payload). Il valoreSENTRY_DSN=vuoto disattiva anche questo path. - Retention GlitchTip: 90 giorni eventi (default, modificabile in
/opt/oasi-glitchtip/deploy/.env).
Cosa NON esce dal perimetro OASI: zero. GlitchTip è self-host sullo stesso box srvai01 — niente subprocessor terzo. Il traffico errors.oasi.systems resta dentro la rete loopback del box (nginx container → host.docker.internal → glitchtip web).
Modalità degraded: senza GlitchTip raggiungibile, le captureException
fanno fallire silenziosamente nel SDK (queue → drop a buffer pieno);
l'applicazione cliente NON è impattata dalla disponibilità del
backend observability.
Classificazione dati e retention
| Categoria | Esempio | Memorizzato dove | Retention |
|---|---|---|---|
| Codice sorgente cliente | il repo intero | temp dir → cancellata in finally{} (durata: secondi a minuti) | non persistito |
| Estratti di codice (snippet) | _smtp.Credentials = new NetworkCredential("x","y"); (≤200 char) | DB HermesDiscovery.remediation | tenant lifetime (cancellabile su richiesta) |
| Artifact analisi | StackReport, CVE list, LLM summary, plan, remediation | DB HermesDiscovery.* | tenant lifetime |
| Audit log | userId+tenantId+action+timestamp+metadata | DB HermesAuditLog | tenant lifetime (append-only) |
| Sessione operatore | WebAuthn credentials, refresh token hashato | DB HermesCredential, HermesSession | 30gg refresh, vita-tenant credentials |
| Segreti operatori | deploy key SSH (priv), PAT GitHub | DB HermesRepo.deployKeyEnc / prTokenEnc (AES-256-GCM) | tenant lifetime (ruotabile) |
| Chiave Anthropic tenant (Sprint 2 §A) | sk-ant-... | DB HermesTenant.anthropicKeyEnc (AES-256-GCM) | tenant lifetime (POST/DELETE per rotazione) |
| Bundle upload (Sprint 2 §B) | .zip / .tar.gz cliente | /tmp/hermes-upload-XXX/ (tempdir) | non persistito — cancellato in finally{} route (~30-180s) + orphan sweep > 60min all'entrypoint container |
| PDF Discovery generato | byte del PDF | non persistito (rendering on-demand) | per-request |
.patch generato | bytes del unified diff | non persistito | per-request |
| Backup DB | snapshot Postgres | provider hosting | per policy del provider (vedi DPA + Subprocessor list) |
Punti di uscita dati dal perimetro OASI
| Destinazione | Cosa esce | Quando | Base giuridica |
|---|---|---|---|
| api.anthropic.com (HTTPS) — chiave OASI | corpus di codice cliente (top-N file) + prompt | Discovery con env.ANTHROPIC_API_KEY configurata (default) + comprehension/remediation step | contratto + DPA OASI↔Anthropic, informativa cliente |
| api.anthropic.com (HTTPS) — chiave TENANT | idem sopra | Discovery quando HermesTenant.anthropicKeyEnc è settato (Sprint 2 §A) | contratto + DPA CLIENTE↔Anthropic; OASI non è più responsabile del trattamento per questo flow |
| api.osv.dev (HTTPS) | nomi+versioni delle dipendenze | Discovery, stadio VulnScanner | dati non personali, finalità di interesse legittimo |
| api.github.com / gitlab.com / bitbucket.org / dev.azure.com (HTTPS) | il branch+commit+PR scritti volontariamente | Apply→PR (provider scelto dal cliente in base al repo registrato — Sprint 3) | contratto col cliente, consenso |
| Stripe webhook (in entrata) | metadata di billing | sub event | contratto OASI↔Stripe |
| Browser operatore (HTTPS, TLS) | PDF, .patch, JSON | per-request | contratto col cliente |
Topologia on-prem (variante — Sprint 4 §C/D/E)
Quando il cliente sceglie il deploy on-prem (deploy/onprem/), la
topologia cambia: la "zona OASI Hermes" si sposta dentro la DMZ
del cliente. OASI non ha visibilità sul dato cliente — nessuna
chiamata outbound verso oasi.systems. Le uniche outbound (se
configurate) sono verso il LLM provider scelto.
┌──────────────────────────────────────────────────────────────┐
│ ZONA "CLIENTE" — DMZ del cliente (tutto in casa) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ HERMES ON-PREM STACK (deploy/onprem/) │ │
│ │ · nginx hardened (TLS cert cliente, 9 headers) │ │
│ │ · hermes-console (read-only fs, cap_drop ALL) │ │
│ │ · postgres (volume backup-ato dal cliente) │ │
│ │ · redis (in-memory only) │ │
│ │ Secrets via Docker Secrets, niente .env plaintext │ │
│ └──────────┬───────────────────────────────────────────┘ │
│ │ HTTPS outbound (uno dei 3 — opt-in cliente) │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ AWS │ │ GCP │ │ Ollama │ │
│ │ Bedrock │ │ Vertex AI │ │ (locale, │ │
│ │ region EU │ │ region EU │ │ no out) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ contratto contratto nessun outbound │
│ cliente↔AWS cliente↔GCP — air-gapped │
└──────────────────────────────────────────────────────────────┘
✗ NIENTE flusso verso "OASI Hermes SaaS"
✗ NIENTE telemetria automatica verso OASI
✗ NIENTE Stripe (licensing one-time, no SaaS billing)
✓ OASI vede SOLO ticket di supporto inviati manualmente
Differenze vs SaaS sul piano del dato:
| Aspetto | SaaS (default) | On-prem |
|---|---|---|
| Codice cliente | transita per OASI Hermes box (srvai01) | resta dentro la DMZ cliente |
| Anthropic outbound | sempre (chiave OASI, salvo §A) | solo se cliente sceglie provider=anthropic (sconsigliato per DMZ chiusa) |
| Bedrock / Vertex | n/a (SaaS = anthropic-only) | DISPONIBILE con DPA cliente↔AWS/GCP, region EU |
| Ollama / vLLM | n/a | DISPONIBILE — zero outbound |
| Stripe | opzionale (PAID gate) | non usato (licensing one-time) |
| VCS provider (Apply→PR) | SaaS chiama api.<provider> con PAT cliente | idem (cliente sceglie quali VCS registrare) |
| Telemetria verso OASI | nessuna automatica | nessuna automatica |
Subprocessor list in modalità on-prem (vedi /legal/subprocessors):
- Anthropic = subprocessor solo se cliente sceglie provider=anthropic.
- AWS = subprocessor del cliente (cliente è contraente diretto), non OASI.
- GCP = idem.
- Ollama / vLLM = nessun subprocessor — locale.
- Stripe / Let's Encrypt / GitHub-GitLab-etc = solo se attivati dal cliente.
- OASI = non è subprocessor del cliente in modalità on-prem (è il fornitore del software, non del servizio — vedi
/legal/dpa§"On-prem deployment" per il modello contrattuale).
Diritti dell'interessato (GDPR, se applicabile)
Hermes non è progettato per processare PII del cliente — il codice sorgente È il dato. Tuttavia, se il codice del cliente contiene dati personali in chiaro (es. credenziali hardcoded, mock data con nomi reali), tali dati transitano in Hermes durante l'analisi.
Diritto di cancellazione: il cliente può richiedere a OASI la
cancellazione completa di tutte le sue Discovery (tabella
HermesDiscovery) entro 30 giorni dalla richiesta scritta a
privacy@oasi.systems. La cancellazione è irreversibile.
Diagrammi più dettagliati (componente-per-componente, in formato
draw.io / lucidchart) sono disponibili sotto NDA su
security@oasi.systems.