Comment utiliser une app Atproto en sécurité
Depuis début septembre 2026, une application développée par @pds.dad et @dame.is est devenue très populaire. Elle propose à chacun de voter pour ses 10 “influential posters” préférés via une interface en ligne hébergée à https://bsky38.com et affiche les 38 comptes ayant reçu le plus de votes. 38 étant un nombre fétiche des habitués de Bluesky, représentant l’âge présumé (absolument non vérifié) de tous ses utilisateurs. Le vote finit le 21 septembre.
Le concept permettant à chaque communauté de se rassembler autour de poulains communs et de défier les autres communautés provoque la viralité de l’application et chacun a pu s’amuser à voir des interactions amusantes s’amplifier les unes avec les autres.
Le 7 septembre à midi, en modifiant un script de https://tangled.org/ptr.pet/hydrant, nous comptions 13665 votants pour 101663 votes, dont 80% n’ont jamais eu dans leur répertoire Atproto un enregistrement autre que ceux de Bluesky. L’expérience est donc devenue pour nous une opportunité d’expliquer plus en détail ce qu’il se passe lorsque vous vous connectez à l’application et quelles sont les bonnes précautions pour protéger votre compte.
1. Vérifier la provenance de l’application
L’utilisateur ne devrait se connecter qu’à des applications dont l’équipe à l’origine est connue, a une réputation en jeu et mérite sa confiance.
Un code open-source, dont le code est disponible à la lecture de tous, ne garantit en rien l’absence de bugs ou l’absence de virus dans ses dépendances. De plus, rien ne permet de garantir que le code visible de tous est bien celui qui fonctionne sur le serveur ou dans le navigateur lors de la visite de l’utilisateur.
Cette application aura des droits d’écriture limités aux lexiques validés par
l’utilisateur, cela ne limite que la forme et non le contenu même. Par exemple,
une application reçoit le droit de stocker dans le PDS d’un utilisateur un
enregistrement app.bsky.feed.post{
"lexicon": 1,
"id": "app.bsky.feed.post",
"defs": {
"main": {
"type": "record",
"description": "Record containing a Bluesky post.",
"key": "tid",
"record": {
"type": "object",
"required": [
"text",
"createdAt"
],
"properties": {
"text": {
"type": "string",
"maxLength": 3000,
"maxGraphemes": 300,
"description": "The primary post content. May be an empty string, if there are embeds."
},
"entities": {
"type": "array",
"description": "DEPRECATED: replaced by app.bsky.richtext.facet.",
"items": {
"type": "ref",
"ref": "#entity"
}
},
"facets": {
"type": "array",
"description": "Annotations of text (mentions, URLs, hashtags, etc)",
"items": {
"type": "ref",
"ref": "app.bsky.richtext.facet"
}
},
"reply": {
"type": "ref",
"ref": "#replyRef"
},
"embed": {
"type": "union",
"refs": [
"app.bsky.embed.images",
"app.bsky.embed.video",
"app.bsky.embed.gallery",
"app.bsky.embed.external",
"app.bsky.embed.record",
"app.bsky.embed.recordWithMedia"
]
},
"langs": {
"type": "array",
"description": "Indicates human language of post primary text content.",
"maxLength": 3,
"items": {
"type": "string",
"format": "language"
}
},
"labels": {
"type": "union",
"description": "Self-label values for this post. Effectively content warnings.",
"refs": [
"com.atproto.label.defs#selfLabels"
]
},
"tags": {
"type": "array",
"description": "Additional hashtags, in addition to any included in post text and facets.",
"maxLength": 8,
"items": {
"type": "string",
"maxLength": 640,
"maxGraphemes": 64
}
},
"createdAt": {
"type": "string",
"format": "datetime",
"description": "Client-declared timestamp when this post was originally created."
}
}
}
},
"replyRef": {
"type": "object",
"required": [
"root",
"parent"
],
"properties": {
"root": {
"type": "ref",
"ref": "com.atproto.repo.strongRef"
},
"parent": {
"type": "ref",
"ref": "com.atproto.repo.strongRef"
}
}
},
"entity": {
"type": "object",
"description": "Deprecated: use facets instead.",
"required": [
"index",
"type",
"value"
],
"properties": {
"index": {
"type": "ref",
"ref": "#textSlice"
},
"type": {
"type": "string",
"description": "Expected values are 'mention' and 'link'."
},
"value": {
"type": "string"
}
}
},
"textSlice": {
"type": "object",
"description": "Deprecated. Use app.bsky.richtext instead -- A text segment. Start is inclusive, end is exclusive. Indices are for utf16-encoded strings.",
"required": [
"start",
"end"
],
"properties": {
"start": {
"type": "integer",
"minimum": 0
},
"end": {
"type": "integer",
"minimum": 0
}
}
}
}
} et donc par conséquent de publier sur
le réseau de Bluesky, elle ne sera pas limitée dans ce qu’elle écrira à
l’intérieur même de la publication. Il faut donc avoir confiance que l’application
n’abusera pas de ce pouvoir et que son comportement correspond à celui attendu
pour son usage.
2. Vérifier l’URL lors de la redirection vers le PDS
Lorsque l’utilisateur se connecte, il doit entrer dans un formulaire son handle: @bob.bsky.social, ou @alice.eurosky.social ou encore son nom de domaine personnalisé: @atproto.fr. Alors l’application va déduire de ce handle le lien vers le PDS de l’utilisateur et va rediriger l’utilisateur vers celui-ci pour que, dans une communication privée Utilisateur-PDS, l’utilisateur confirme au PDS que l’application a le droit de modifier ou lire des données dans les limites proposées par l’application. Cette communication privée entre l’utilisateur et son PDS peut nécessiter que l’utilisateur s’authentifie auprès du PDS. La méthode d’authentification la plus courante étant d’entrer à nouveau son mot de passe.
Il est donc crucial pour l’utilisateur de confirmer que le service avec lequel il s’authentifie correspond bien à son PDS et que l’application ne l’a pas redirigé vers un service malveillant qui, en faisant office d’intermédiaire, intercepte le mot de passe. Pour confirmer qu’il s’agit de son PDS, l’utilisateur a pour le moment un seul moyen: vérifier correctement l’URL avant d’entrer des informations dans un formulaire.

alt
Page de connexion du PDS lors d'une connexion à Tangled : la barre d'adresse du navigateur affiche pds.atproto.fr, et la page propose de s'authentifier par passkey ou par mot de passe.
Malheureusement, chaque année plusieurs dizaines de millions de Français sont confrontés à une tentative d’hameçonnage et si cet article a pour but de faire un peu de prévention, il est impossible de savoir dans quel état de vigilance l’utilisateur sera lorsqu’il s’authentifiera. Heureusement, pour se protéger, il existe d’autres moyens d’authentification que le formulaire de mot de passe, notamment le système dit passkey qui génère une paire de clefs rattachée au domaine et dont l’usage est donc inopérant via un autre (celui du service malveillant).
3. Vérifier les permissions
Une fois l’utilisateur redirigé vers le PDS et authentifié auprès de lui, le PDS lui indique les droits auxquels l’application demande à avoir accès.*Nous détaillons chaque permission (ou scope) que peut demander une application dans l’entrée Permissions du wiki.

alt
Page d'autorisation d'un PDS de Bluesky pour tangled.org : une fenêtre liste les scopes demandés, des enregistrements repo:sh.tangled.* et des appels rpc:sh.tangled.*.
La logique est de se poser la question, en relisant chaque permission :
est-il cohérent que l’application en question demande des droits sur ce
lexique en particulier ? Pourquoi, par exemple, https://tangled.org
demanderait-il des droits sur le lexique app.bsky.feed.post{
"lexicon": 1,
"id": "app.bsky.feed.post",
"defs": {
"main": {
"type": "record",
"description": "Record containing a Bluesky post.",
"key": "tid",
"record": {
"type": "object",
"required": [
"text",
"createdAt"
],
"properties": {
"text": {
"type": "string",
"maxLength": 3000,
"maxGraphemes": 300,
"description": "The primary post content. May be an empty string, if there are embeds."
},
"entities": {
"type": "array",
"description": "DEPRECATED: replaced by app.bsky.richtext.facet.",
"items": {
"type": "ref",
"ref": "#entity"
}
},
"facets": {
"type": "array",
"description": "Annotations of text (mentions, URLs, hashtags, etc)",
"items": {
"type": "ref",
"ref": "app.bsky.richtext.facet"
}
},
"reply": {
"type": "ref",
"ref": "#replyRef"
},
"embed": {
"type": "union",
"refs": [
"app.bsky.embed.images",
"app.bsky.embed.video",
"app.bsky.embed.gallery",
"app.bsky.embed.external",
"app.bsky.embed.record",
"app.bsky.embed.recordWithMedia"
]
},
"langs": {
"type": "array",
"description": "Indicates human language of post primary text content.",
"maxLength": 3,
"items": {
"type": "string",
"format": "language"
}
},
"labels": {
"type": "union",
"description": "Self-label values for this post. Effectively content warnings.",
"refs": [
"com.atproto.label.defs#selfLabels"
]
},
"tags": {
"type": "array",
"description": "Additional hashtags, in addition to any included in post text and facets.",
"maxLength": 8,
"items": {
"type": "string",
"maxLength": 640,
"maxGraphemes": 64
}
},
"createdAt": {
"type": "string",
"format": "datetime",
"description": "Client-declared timestamp when this post was originally created."
}
}
}
},
"replyRef": {
"type": "object",
"required": [
"root",
"parent"
],
"properties": {
"root": {
"type": "ref",
"ref": "com.atproto.repo.strongRef"
},
"parent": {
"type": "ref",
"ref": "com.atproto.repo.strongRef"
}
}
},
"entity": {
"type": "object",
"description": "Deprecated: use facets instead.",
"required": [
"index",
"type",
"value"
],
"properties": {
"index": {
"type": "ref",
"ref": "#textSlice"
},
"type": {
"type": "string",
"description": "Expected values are 'mention' and 'link'."
},
"value": {
"type": "string"
}
}
},
"textSlice": {
"type": "object",
"description": "Deprecated. Use app.bsky.richtext instead -- A text segment. Start is inclusive, end is exclusive. Indices are for utf16-encoded strings.",
"required": [
"start",
"end"
],
"properties": {
"start": {
"type": "integer",
"minimum": 0
},
"end": {
"type": "integer",
"minimum": 0
}
}
}
}
} ?
Certaines implémentations de PDS permettent, au moment de la vérification des autorisations, d’en décocher certaines. C’est une nouvelle couche de sécurité bienvenue, qui permet à l’utilisateur de n’accorder que le minimum de droits dans le contexte d’usage qui est le sien. S’il sait que, durant cette session, il n’utilisera qu’une fonctionnalité de l’application, il peut alors limiter les droits au strict nécessaire.

alt
Page d'autorisation du PDS d'atproto.fr pour Tangled : chaque permission demandée est une case à cocher, regroupée par catégorie, que l'utilisateur peut décocher avant d'autoriser.
Conclusion
Ces trois vérifications sont à la portée de chaque utilisateur, mais elles demandent une vigilance qu’il n’est pas raisonnable d’exiger à chaque connexion. La responsabilité ne repose pas uniquement sur lui. Les concepteurs de PDS et les créateurs d’applications ont un rôle important à jouer pour améliorer l’expérience utilisateur : présenter les droits demandés dans un langage compréhensible, expliquer à quoi sert chaque permission dans le contexte de l’application, et permettre de n’accorder que le nécessaire. Un design qui rend les droits lisibles fait plus pour la sécurité des comptes que n’importe quel article de prévention.
Chargement…