đ Les clĂ©s secrĂštes deviennent des donnĂ©es d'application
le 20 septembre 2026
đ Les clĂ©s secrĂštes deviennent des donnĂ©es d'application
La plupart des applications Symfony que jâĂ©cris considĂšrent encore la clĂ© du modĂšle comme faisant partie de lâinfrastructure :
ANTHROPIC_API_KEY=...
Cette ligne est simple car la propriĂ©tĂ© est simple. Lâidentifiant appartient au dĂ©ploiement. Les opĂ©rateurs lâenregistrent dans .env.local ou dans un coffre-fort de secrets. Le conteneur lâinjecte une seule fois au dĂ©marrage. Lâapplication nâa jamais Ă lâaccepter, Ă le stocker ou Ă dĂ©cider quand il est autorisĂ© Ă exister en mĂ©moire.
Le modÚle « Bring-your-own-key » (apportez votre propre clé) change le propriétaire.
Que se passe-t-il lorsque la clĂ© appartient Ă lâutilisateur Ă la place ?
Elle arrive via HTTP. Elle est associĂ©e Ă une entitĂ© de lâapplication. Elle doit perdurer dâune requĂȘte Ă lâautre. Elle ne doit pas ĂȘtre stockĂ©e en clair, ne doit pas ĂȘtre rĂ©insĂ©rĂ©e dans le code HTML, et ne doit ĂȘtre dĂ©chiffrĂ©e que lorsquâun Ă©lĂ©ment a rĂ©ellement besoin dâappeler un fournisseur. Ce fournisseur peut engager des dĂ©penses.
Le fichier .env nâest plus la solution. Lâidentifiant est devenu une donnĂ©e dâapplication.
LâexpĂ©rience de cette semaine, navi-key-management, est une petite application Symfony 8.2 qui implĂ©mente ce cycle de vie puis le mesure. Je rapporte ici ce qui a Ă©tĂ© exĂ©cutĂ© le 19/09/2026 : PHP 8.5.4, Symfony 8.2.x-dev, SQLite, Sodium local. Symfony KeyManagement est expĂ©rimental. Câest ce quâindiquent ses docblocks dâinstallation. ConsidĂ©rez chaque signature ici comme un instantanĂ©, et non comme une promesse concernant la version stable 8.2.
BYOK modifie les limites
Le chemin que jâai codĂ© est celui que je souhaitais pouvoir lire :
Utilisateur
â
Formulaire Symfony
â
Chiffrement par KeyManagement
â
Texte chiffré SQLite
â
DĂ©chiffrement au moment de lâexĂ©cution
â
Darkwood Navi
â
Symfony AI
â
Anthropic / fournisseur
Chaque flĂšche reprĂ©sente un type de traitement diffĂ©rent. Le formulaire ne sait pas comment une requĂȘte de chat est construite. Navi ne stocke pas les secrets. Symfony AI ne dĂ©tient pas la ligne de la base de donnĂ©es. KeyManagement ne communique pas avec Claude.
Je nâai pas ajoutĂ© de couche de rĂ©fĂ©rentiel ni de deuxiĂšme SDK dâIA. Les abstractions intĂ©ressantes existaient dĂ©jĂ dans la pile. Lâune dâentre elles pouvait facilement ĂȘtre mal nommĂ©e.
darkwood/navi est le shell du workflow : Action, Context, WorkflowRunner, Event. Il ne comporte ni client Anthropic ni type pour une clĂ© API. Câest Symfony AI qui communique avec le modĂšle. La chaĂźne dâexĂ©cution dans ce rĂ©fĂ©rentiel est la suivante :
AiConnection
â
CredentialProtector::decrypt()
â
WorkflowRunner
â
TestAiConnectionAction
â
Anthropic\Factory::createPlatform($apiKey)
â
provider
Navi orchestre une étape. Il ne remplace pas Symfony AI.
Il y a eu un problĂšme de compatibilitĂ© des paquets qui mĂ©rite dâĂȘtre mentionnĂ© ici, car il est bien rĂ©el. Le paquet Packagist darkwood/navi ^1.0 nĂ©cessite toujours Symfony 8.0. Cette application a besoin de la version 8.2.x-dev ; Navi est donc un dĂ©pĂŽt de chemin dâaccĂšs vers le monorepo local (8.1.x-dev). ExpĂ©rimenter avant que les contraintes de la version finale ne soient fixĂ©es, c'est ça : un lien symbolique, pas un slogan.
Symfony se dote d'une primitive de gestion des clés
Une fois que la clé est considérée comme une donnée d'application, la premiÚre piÚce manquante n'est pas un autre wrapper AI. C'est un moyen de chiffrer un petit secret sans avoir à inventer un service de cryptographie.
KeyManagement de Symfony 8.2 est cette fonctionnalitĂ© de base. Jâai installĂ© symfony/key-management et symfony/aws-key-management sur la version 8.2.x-dev et jâai utilisĂ© les interfaces que le conteneur connecte automatiquement :
$ciphertext = $this->encrypter->encrypt($this->keyId, $apiKey);
$connection->setCiphertextBlob(base64_encode($ciphertext->blob));
$connection->setKmsKeyId($ciphertext->keyId);
La mĂ©thode encrypt() renvoie un objet Ciphertext, et non une chaĂźne de caractĂšres. La ligne regroupe le blob et lâidentifiant de clĂ©. Lâapplication injecte les interfaces EncrypterInterface et DecrypterInterface. Le chiffrement par enveloppe est disponible dans le mĂȘme bundle. Je ne lâai pas utilisĂ©. Une clĂ© API fait plusieurs centaines dâoctets, ce qui est bien en deçà de la limite de 4 Ko imposĂ©e par le chiffrement direct sur AWS KMS. Le chiffrement dâenveloppe est destinĂ© aux fichiers et aux documents volumineux, ou Ă une politique stipulant que le KMS ne doit jamais avoir accĂšs au texte en clair de lâapplication. Ce nâest pas obligatoire, car le composant le fournit.
La configuration que jâai finalement retenue nâest pas le DSN dâune seule ligne que jâavais dâabord essayĂ©.
key_management:
clients:
app: 'sodium://?keys[app-key]=%env(DEV_KMS_KEY)%'
default_client: app
when@prod:
key_management:
clients:
app: 'aws-kms://default?region=%env(AWS_KMS_REGION)%'
Un simple '%env(KMS_DSN)%' ne se compile pas. Lâarborescence de configuration expĂ©rimentale marque le DSN du client comme cannotBeEmpty, et une variable dâenvironnement entiĂšrement substituĂ©e est traitĂ©e comme vide au moment de la compilation :
Le chemin « key_management.clients.app » ne peut pas contenir de variable dâenvironnement
lorsque les valeurs vides ne sont pas autorisĂ©es par dĂ©finition et font lâobjet dâune validation.
Le modĂšle de fonctionnement conserve un schĂ©ma littĂ©ral â sodium:// dans dev/test, aws-kms:// dans prod â et ne remplace par la variable dâenvironnement que la clĂ© ou la rĂ©gion.
dev / test
â
Sodium â testĂ© dans ce dĂ©pĂŽt
prod
â
AWS KMS â configurĂ©, non appelĂ©
Câest grĂące Ă Sodium que lâexpĂ©rience est reproductible localement. Le processus dispose de DEV_KMS_KEY. Je lâai gĂ©nĂ©rĂ©e avec sodium_crypto_aead_xchacha20poly1305_ietf_keygen() et Base64UrlSafe. Quiconque peut lire cette valeur dâenvironnement peut dĂ©chiffrer toutes les clĂ©s API stockĂ©es. Câest acceptable pour une dĂ©monstration sur un ordinateur portable. Ce nâest pas le cas en production.
AWS KMS est la frontiĂšre externe vers laquelle pointe le DSN de production. Le pont AWS chiffrer cĂŽtĂ© serveur ; la clĂ© principale est censĂ©e rester dans KMS. Je nâai pas envoyĂ© de requĂȘte Ă AWS. Le bloc de production correspond Ă la configuration, et non Ă un rĂ©sultat de test.
Le badge de lâinterface utilisateur nâanalyse pas le DSN. KMS_BACKEND est une Ă©tiquette (Sodium ou AWS KMS), de sorte que le matĂ©riel de chiffrement ne devient jamais une variable Twig.
La clĂ© de dĂ©veloppement enregistrĂ©e dans le fichier .env de lâexpĂ©rience est une clĂ© de dĂ©veloppement uniquement. GĂ©nĂ©rez une clĂ© privĂ©e Sodium avant dâutiliser ce modĂšle pour une application rĂ©elle.
Une petite expérience
Le modĂšle est constituĂ© dâune seule entitĂ©, AiConnection : libellĂ©, fournisseur, modĂšle, point de terminaison facultatif, ciphertextBlob, kmsKeyId, horodatages. Il nây a pas de colonne contenant la clĂ© API en clair.
Lâinterface utilisateur se compose de quatre routes et dâun panneau sombre. Elle a pour but de rendre le cycle de vie visible : une liste avec des badges « chiffrĂ© » et « backend », un formulaire, une action de test, une page de rĂ©sultats. Il ne sâagit pas dâun produit de type tableau de bord.
GET /
GET|POST /connections/new
GET|POST /connections/{id}/edit
POST /connections/{id}/test
Cela constitue l'intégralité de l'interface de l'application.
Le formulaire est désormais une frontiÚre de sécurité
La clĂ© Ă©tait auparavant introduite via dotenv. Elle est dĂ©sormais saisie via un formulaire. Le formulaire fait donc dĂ©sormais partie intĂ©grante de la conception de la sĂ©curitĂ©, et nâest plus simplement une « enveloppe » dâun DTO.
Symfony 8.2 peut dĂ©duire le type de formulaire Ă partir de la classe qui contient les donnĂ©es. Ce DTO comporte cinq champs. Câest Ă cette taille que les nouveaux attributs sont adaptĂ©s.
#[AsFormType]
final class AiConnectionInput
{
#[FormField(ChoiceType::class, [
'choices' => [
'Anthropic' => 'anthropic',
'OpenAI' => 'openai',
],
])]
public string $provider = 'anthropic';
#[FormField(PasswordType::class, [
'always_empty' => true,
'attr' => ['autocomplete' => 'new-password'],
])]
#[Assert\NotBlank(groups: ['create'])]
public ?string $apiKey = null;
}
createForm(AiConnectionInput::class, $input) suffit. Il n'y a pas d'AbstractType. Le nom compilé est ai_connection_input.
Le comportement de sĂ©curitĂ© que je souhaitais est assurĂ© Ă la fois par les attributs et par le contrĂŽleur. Le champ « key » correspond Ă la saisie dâun mot de passe. always_empty empĂȘche le navigateur dâafficher une valeur. Lors de la modification, le contrĂŽleur copie le libellĂ©, le fournisseur, le modĂšle et le point de terminaison dans le DTO et laisse apiKey nul. Un envoi sans valeur nâappelle pas encrypt() ; le texte chiffrĂ© existant est conservĂ©. NotBlank nâapparaĂźt que dans le groupe create.
Les formulaires basĂ©s sur des attributs mâont Ă©vitĂ© dâĂ©crire une classe que jâaurais autrement dĂ» crĂ©er. Ils nâont pas supprimĂ© les groupes de validation ni la protection CSRF. La recette Flex « stateless-CSRF » a laissĂ© value="csrf-token" dans le code HTML et prĂ©voyait un contrĂŽleur Stimulus que cette application ne fournit pas. La protection CSRF par session a permis de faire fonctionner un envoi normal via le navigateur. Câest une petite leçon de la version 8.2 qui vient complĂ©ter la grande : les nouveaux attributs de formulaire sâintĂšgrent bien dans un DTO de configuration, et ils ne suppriment pas le reste de la pile de formulaires.
Texte chiffré dans SQLite
AprĂšs lâenvoi par le navigateur dâune fausse clĂ©, SQLite a conservĂ© des mĂ©tadonnĂ©es ainsi quâun blob :
$ sqlite3 var/data.db "SELECT kms_key_id, length(ciphertext_blob), instr(ciphertext_blob, 'sk-ant') FROM ai_connection;"
app-key|96|0
instr(ciphertext_blob, 'sk-ant') = 0. Une comparaison LIKE avec la valeur soumise a également donné 0. Le texte stocké est un Base64 opaque de Ciphertext::$blob.
Il sâagit de la preuve de persistance la plus solide de la semaine, mais elle prĂ©sente une limite claire. Elle prouve que le texte en clair identifiable est absent de cette colonne. Il ne sâagit pas dâun audit cryptographique. Elle ne prouve pas que lâalgorithme de chiffrement soit Sodium XChaCha, ne prouve pas la gestion des nonces, et ne prouve pas quâun attaquant disposant de la clĂ© principale de dĂ©veloppement soit bloquĂ©. PHPUnit effectue le mĂȘme contrĂŽle dâabsence sur la rĂ©ponse HTTP : la clĂ© que jâai publiĂ©e nâapparaĂźt pas dans le code HTML.
La page de liste affiche chiffrĂ© et Sodium. Elle nâaffiche pas la clĂ©.
Déchiffrer au dernier moment responsable
La consultation dâune connexion ne dĂ©chiffre rien. Sa modification ne dĂ©chiffre rien. La clĂ© nâest reconstruite que lorsquâune action dâIA en a besoin.
$apiKey = $this->credentials->decrypt($connection);
try {
$result = $this->workflows->run(
Context::fromArray([
'connection_id' => $connection->getId(),
'provider' => $connection->getProvider(),
'model' => $connection->getModel(),
]),
[new TestAiConnectionAction($apiKey, /* fournisseur, modĂšle, point de terminaison */)],
);
} finally {
$apiKey = str_repeat("\0", \strlen($apiKey));
unset($apiKey);
}
texte chiffré stocké
â
Test de connexion / sondage
â
déchiffrement
â
constructeur de TestAiConnectionAction
â
Factory::createPlatform($apiKey)
â
une invite
L'action marque l'argument du constructeur #[\SensitiveParameter]. Le Context transporte des identifiants. Il ne transporte pas le secret. Câest dĂ©libĂ©rĂ© : le Context de Navi dispose de la mĂ©thode toArray(). Y placer la clĂ© transformerait lâĂ©tat de lâorchestration en une fuite dâinformations.
app:probe-connection a signalĂ© context_has_secret: no aprĂšs une exĂ©cution en production. Navi peut orchestrer lâappel sans intĂ©grer les identifiants dans le flux de travail.
Au sein de lâaction, Symfony AI est instanciĂ© au moment de lâexĂ©cution. Une variable ANTHROPIC_API_KEY au niveau du conteneur dans ai.yaml aurait contrecarrĂ© le principe BYOK.
AnthropicFactory::createPlatform(
$this->apiKey,
$this->httpClient,
baseUrl: $this->endpoint ?: 'https://api.anthropic.com',
);
La prompt ne comporte quâune seule ligne : RĂ©ponds par le mot pong. Si le client HTTP renvoie un message contenant la clĂ©, SecretRedactor remplace cette sous-chaĂźne avant que lâerreur nâatteigne Twig.
Une requĂȘte ayant Ă©chouĂ© peut tout de mĂȘme nous apporter des informations
Jâai utilisĂ© un identifiant Anthropic disponible localement via app:probe-connection. Je ne vais pas lâafficher ici.
plaintext_in_ciphertext: no
decrypt_matches: yes
context_has_secret: no
test_ok: false
test_error: Votre solde de crĂ©dit est trop faible pour accĂ©der Ă lâAPI Anthropic. ...
Le chiffrement et le dĂ©chiffrement ont concordĂ©. La clĂ© Ă©tait absente de Navi Context. La requĂȘte a quittĂ© le processus et a atteint Anthropic. Anthropic a rĂ©pondu par une erreur de facturation.
Une erreur de facturation reste une preuve de la distance parcourue par la requĂȘte. Une rĂ©ponse indiquant une clĂ© invalide aurait signifiĂ© que nous ne nous Ă©tions jamais authentifiĂ©s. Nous sommes allĂ©s plus loin que cela.
Il ne sâagit pas dâune opĂ©ration rĂ©ussie. Il ne sâagit pas dâune utilisation de jeton. Il ne sâagit pas dâun reçu. Le parcours de lâinterface utilisateur avec une fausse clĂ© affichait « La clĂ© API est invalide. » et aucune information confidentielle. Les deux parcours dâerreur Ă©taient utiles. Aucun des deux ne correspond Ă une conversation aboutie.
Les clĂ©s ont un rayon dâimpact Ă©conomique
Lâarticle « Cost of a diff » de Guillaume Moigneu est Ă lâorigine dâune idĂ©e, et non de ce code : une action dâIA a un coĂ»t Ă©conomique observable. Des jetons dâentrĂ©e, des jetons de sortie, des jetons mis en cache, un modĂšle, un tarif.
Un secret qui dĂ©verrouille un fournisseur dâIA ne sert donc pas uniquement au contrĂŽle dâaccĂšs. Il protĂšge quelque chose qui peut dĂ©penser de lâargent.
Lâapplication dispose du chemin comptable. TestAiConnectionAction lit lâinterface TokenUsageInterface de Symfony AI lorsque le rĂ©sultat de lâappel contient token_usage :
jetons de prompt â entrĂ©e
jetons de complĂ©tion â sortie
crĂ©ation de cache â cache_write
lecture de cache â cache_read
jetons mis en cache â mis en cache
Les mĂ©triques manquantes restent null et sâaffichent comme « non rapportĂ©es ». AiCostTable applique ensuite une liste explicite par million de dollars US datĂ©e du 19/09/2026 :
usd = entrĂ©e Ă taux_dâentrĂ©e / 1_000_000
+ sortie Ă taux_de_sortie / 1_000_000
+ cache_write Ă cache_write_rate / 1_000_000 (si la table en comporte un)
+ cache_read Ă cache_read_rate / 1_000_000 (si la table en comporte un)
Claude Sonnet 4.5 : dans cette table, les valeurs sont respectivement de 3 $ / 15 $ / 3,75 $ / 0,30 $. Une simulation locale avec des chiffres fictifs (42 entrĂ©es, 8 sorties) donne 0,000246 $. Ce chiffre est issu dâune vĂ©rification sur tableur. Il ne sâagit pas dâun reçu du fournisseur.
Aucune transaction payante nâa abouti cette semaine. Le chemin de facturation existe. Il nâa pas Ă©tĂ© validĂ© par rapport aux mĂ©tadonnĂ©es dâutilisation fournies par Anthropic. Je ne vais pas inventer de jetons pour donner lâimpression que lâinterface utilisateur est terminĂ©e.
Le fournisseur nâest pas un point de terminaison
Une discussion sur Symfony AI cette semaine a indiquĂ© que Claude, via un abonnement Azure AI, fonctionnait en redirigeant le pont Anthropic existant vers une URL de messages compatible avec Anthropic, plutĂŽt quâen ajoutant une nouvelle abstraction Azure-Claude.
La structure rapportée était la suivante :
<resource>.services.ai.azure.com/anthropic/v1/messages
Le test rapporté comprenait un échange de messages complet et la mise en cache des invites. Je ne répéterai pas les identités privées mentionnées dans ce fil de discussion.
La mĂ©thode Factory::createPlatform() dâAnthropic dĂ©jĂ installĂ©e dispose dĂ©jĂ de $baseUrl. ModelClient envoie des requĂȘtes vers {baseUrl}/v1/messages. Le point de terminaison facultatif du formulaire correspond donc Ă la base Azure sans ce suffixe :
https://<resource>.services.ai.azure.com/anthropic
baseUrl est configurĂ©e. Lâappel Ă Azure Anthropic nâa pas Ă©tĂ© effectuĂ©.
Bedrock ne fonctionne pas de la mĂȘme maniĂšre. Il sâauthentifie Ă lâaide dâidentifiants AWS, et non dâune clĂ© Anthropic associĂ©e Ă un hĂŽte. La compatibilitĂ© Ă©tait un problĂšme de point de terminaison pour la passerelle Azure. Il sâagit dâun problĂšme de modĂšle dâauthentification pour Bedrock. Je nâai pas ajoutĂ© de couche Bedrock.
LâidentitĂ© du fournisseur, le point de terminaison de transport et le stockage des identifiants sont des prĂ©occupations distinctes.
Parfois, la compatibilitĂ© est une question de configuration, et non une autre abstraction. Câest la mĂȘme habitude que de garder Navi hors de la persistance et Symfony AI hors du formulaire.
Sodium en local, KMS en externe
Je ne vais pas transformer cet article en tutoriel de cryptographie. La différence opérationnelle suffit.
Avec Sodium, lâapplication dĂ©tient la clĂ© de chiffrement et peut dĂ©chiffrer chaque ligne quâelle a Ă©crite. LâexpĂ©rience est exĂ©cutable sans compte AWS. La compromission de DEV_KMS_KEY Ă©quivaut Ă la compromission des clĂ©s API stockĂ©es.
Avec AWS KMS, lâapplication interroge un service externe. La clĂ© principale est censĂ©e rester en dehors du stockage de lâapplication. La compromission du fichier .env ne suffit pas ; un attaquant a toujours besoin des identifiants AWS et dâune politique de clĂ© autorisant le dĂ©chiffrement.
Il sâagit dâune amĂ©lioration en matiĂšre de gestion des clĂ©s. Ce nâest pas une protection miracle contre un environnement dâexĂ©cution compromis. Si lâapplication en cours dâexĂ©cution peut appeler decrypt(), un attaquant suffisamment prĂ©sent peut Ă©galement lâappeler. Lâavantage rĂ©side dans le fait que la clĂ© principale nâest pas une chaĂźne dâoctets placĂ©e Ă cĂŽtĂ© du texte chiffrĂ©, et que le chiffrement et le dĂ©chiffrement peuvent ĂȘtre auditĂ©s en dehors de lâapplication. Jâai vĂ©rifiĂ© le premier cas de figure. Jâai configurĂ© le second.
LâIA modifie les deux facettes de la sĂ©curitĂ©
Fabien Potencier a Ă©crit ce mois-ci que Symfony recevait auparavant environ un rapport de sĂ©curitĂ© par trimestre et quâil en reçoit dĂ©sormais au moins un par jour. Les agents analysent une nouvelle version dĂšs sa sortie. Un rapport arrivant dans la boĂźte de rĂ©ception doit ĂȘtre considĂ©rĂ© comme dĂ©jĂ public. Les embargos partaient du principe que la dĂ©couverte dâune faille coĂ»tait cher.
Il ne sâagit pas dâun CVE dans cette application. Je nâassocie pas cette expĂ©rience Ă un avis numĂ©rotĂ©.
Le lien pertinent est dâordre Ă©conomique. Les agents rĂ©duisent le coĂ»t de la recherche, dans une base de code, de lâendroit oĂč un secret fuit. ParallĂšlement, un identifiant gĂ©nĂ©rĂ© par lâIA a une valeur financiĂšre directe : il peut ĂȘtre dĂ©pensĂ©. Ces deux faits combinĂ©s expliquent pourquoi la gestion BYOK (Bring Your Own Key) relĂšve de lâarchitecture de lâapplication plutĂŽt que de la gestion du dĂ©ploiement.
Les choix concrets dans le dĂ©pĂŽt sont volontairement simples : pas de colonne en texte clair, pas de saisie de mot de passe, pas de rĂ©affichage, dĂ©cryptage uniquement pour lâexĂ©cution, clĂ© absente de Navi Context, faux fichier .env.example, badge backend sans DSN. KeyManagement se charge du chiffrement. Le reste relĂšve des bonnes pratiques de sĂ©curitĂ©, car lâobjet que nous protĂ©geons est dĂ©sormais une ligne de base de donnĂ©es.
Ce qui a réellement fonctionné
Ces vérifications ont été effectuées.
Container, Twig et YAML ont passé les vérifications de syntaxe sans erreur.
PHPUnit : 2 tests, 15 assertions, OK. Un test effectue un chiffrement et un dĂ©chiffrement via Sodium et vĂ©rifie que le texte en clair est absent du blob de lâentitĂ©. Lâautre envoie le formulaire basĂ© sur les attributs et vĂ©rifie que la clĂ© est absente de SQLite et du code HTML.
Dans le navigateur, jâai ajoutĂ© une connexion, jâai vu les badges « cryptĂ© » et « Sodium », jâai ouvert lâĂ©dition avec un champ de clĂ© API vide, puis jâai testĂ© un faux identifiant. Le fournisseur a rĂ©pondu : « ClĂ© API non valide. »
SQLite aprĂšs cette soumission :
instr(ciphertext_blob, 'sk-ant') = 0
La sonde Anthropic en direct :
chiffrement/déchiffrement : correspondance
identifiants dans le contexte Navi : non
fournisseur atteint : oui
rĂ©sultat : erreur de facturation â solde crĂ©diteur trop faible
Telles sont les conclusions que je suis prĂȘt Ă dĂ©fendre.
Ce que je nâai pas vĂ©rifiĂ©
AWS KMS a Ă©tĂ© configurĂ© mais nâa pas Ă©tĂ© appelĂ©.
La baseUrl dâAnthropic sur Azure a Ă©tĂ© configurĂ©e mais nâa pas Ă©tĂ© appelĂ©e.
Aucune opĂ©ration payante sur Anthropic nâa abouti. Par consĂ©quent, aucun jeton nâa Ă©tĂ© rĂ©ellement reçu, et la comptabilisation des coĂ»ts nâa pas Ă©tĂ© validĂ©e par rapport Ă une rĂ©ponse positive du fournisseur.
KeyManagement reste expérimental.
« ImplĂ©mentĂ© », « configurĂ© », « testĂ© » et « vĂ©rifiĂ© » sont quatre termes distincts. Cette semaine, jâai utilisĂ© les quatre. Je ne les confonds pas.
Conclusion
Le fichier .env est un bon modÚle lorsque le secret appartient au déploiement.
Le BYOK change de propriétaire.
DĂšs lors quâun identifiant appartient Ă un utilisateur de lâapplication, il nĂ©cessite un cycle de vie propre Ă lâapplication : saisie, chiffrement, persistance, exĂ©cution, observabilitĂ© et, Ă terme, rotation. La gestion des clĂ©s de Symfony 8.2 est une primitive utile pour lâĂ©tape de chiffrement. Il est expĂ©rimental, et son arbre de configuration est pointilleux sur les variables dâenvironnement, mais les interfaces que jâai testĂ©es â chiffrer un petit secret, stocker un Ciphertext, dĂ©chiffrer au dernier moment â sont celles dont BYOK a rĂ©ellement besoin.
Navi nâa pas besoin de devenir un gestionnaire de secrets. Symfony AI nâa pas besoin de gĂ©rer la persistance. Le formulaire nâa pas besoin de savoir comment la requĂȘte IA est construite.
Chaque couche peut rester lĂ©gĂšre. La clĂ© nâest plus une ligne dans .env. Câest une ligne de table, un texte chiffrĂ©, un argument de constructeur et un Ă©lĂ©ment de ligne. Câest lĂ tout le changement.