Qui peut voir quoi
Un tableau de bord que n'importe qui muni de l'URL peut lire est une capture d'écran avec des étapes en plus. La raison de garder les tableaux de bord dans une plateforme maintenue plutôt que dans un dossier de HTML exporté est que la plateforme se souvient de qui est autorisé à les ouvrir — et continue de s'en souvenir après le départ de la personne qui les a construits.
Cette page est la carte honnête de ce modèle : ce qu'il applique aujourd'hui, où il s'arrête, et quelles parties vous pouvez atteindre depuis le code.
Les trois rôles
Chaque adhésion à un compte porte exactement un rôle :
| Rôle | Valeur | Ce que cela signifie |
|---|---|---|
| Admin | admin | Contourne chaque vérification de droits d'accès. Gère les membres, les groupes et les octrois. |
| Créateur | creator | Peut créer et modifier. Soumis aux droits d'accès. |
| Visionneur | viewer | Lecture seule. Refusé sur toute route qui modifie. |
Trois choses valent la peine d'être connues avant de concevoir autour de cela.
Le rôle vit sur la ligne d'adhésion, pas dans le jeton. Le JWT ne porte aucune revendication de rôle, donc chaque décision de rôle est une lecture en base de données sur la paire compte/utilisateur. C'est un peu plus lent et beaucoup plus difficile à falsifier.
Il échoue de façon fermée. Une adhésion dont le rôle stocké est non reconnu — une
valeur héritée, une faute de frappe — se résout en viewer, pas en admin.
Admin est un contournement, pas un rang supérieur. Un admin n'est pas « un créateur avec plus d'octrois » : la vérification de permission court-circuite vers autoriser avant même la consultation d'un octroi. Vous ne pouvez pas refuser un admin l'accès à un dossier.
Une clé API se résout vers son utilisateur créateur, avec le rôle de cet utilisateur. Une clé générée par un admin est une clé admin, et tout ce qui suit s'applique à elle exactement comme cela s'applique à cette personne dans le navigateur. Il n'existe pas de portée de permission séparée et plus étroite pour les clés, et une clé n'expire pas quand le rôle de son créateur est rétrogradé — révoquez la clé.
Vingt-quatre routes sont délibérément fermées aux clés et nécessitent une vraie session de navigateur : la facturation et le paiement (16 d'entre elles), la gestion des clés API elle-même, la lecture de votre abonnement, la suppression de compte, le changement de mot de passe, la télémétrie, et la signature de jeton d'intégration. Tout le reste accepte une clé — y compris chaque route de droits d'accès, de groupe, de dossier, de tableau de bord et de jeu de données.
Une exception délibérée. Basculer un tableau de bord en public est
contrôlé par un interrupteur de compte, apiKeysMayPublish, qui vaut false par
défaut et ne peut être activé que par un admin dans un navigateur. Donc une clé — et par
conséquent un agent qui la détient — peut construire, publier et partager à l'intérieur de
votre compte, mais ne peut pas par défaut être ce qui rend quelque chose lisible par le
monde entier. Voir
Publier et partager.
Groupes
Un groupe est un sac nommé d'utilisateurs à l'intérieur d'un compte. Vous accordez au groupe ; l'adhésion change sans toucher à l'octroi.
Deux limites, toutes deux délibérées :
- Les groupes contiennent des utilisateurs, pas d'autres groupes. Il n'y a pas d'imbrication, donc il n'y a pas de hiérarchie de groupes à raisonner.
- Les lectures sont ouvertes à chaque membre du compte ; les écritures sont réservées aux admins. N'importe quel membre peut lister les groupes et voir qui en fait partie. Seul un admin peut créer, renommer, supprimer, ou changer l'adhésion.
Les groupes sont dans le SDK :
from vibedasher.models import GroupCreate, GroupUpdate
created = client.groups.create(GroupCreate(name="analytics", description="Read-only analysts"))
client.groups.update(created.group.id, GroupUpdate(user_ids_to_add=["<user-id>"]))
for g in client.groups.list().groups:
print(g.name, g.user_count)
const created = await client.groups.create({ name: 'analytics', description: 'Read-only analysts' });
await client.groups.update(created.group.id, { userIdsToAdd: ['<user-id>'] });
const { groups } = await client.groups.list();
Droits d'accès — l'octroi porte sur le dossier
C'est la phrase la plus importante de cette page, et celle qui a le plus de chances de vous surprendre :
Un octroi nomme un dossier, jamais un tableau de bord ou un jeu de données individuel.
Un droit d'accès est une ligne de la forme :
(account, folder, resource type, permission, principal) → allow | deny
- resource type —
datasetouviz. Ce sont les deux qu'un octroi peut nommer ; voir Ce qu'un octroi peut nommer pour ce qui gouverne tout le reste. - permission —
view,edit,delete,create. - principal — un utilisateur ou un groupe.
- allow / deny — les deux types d'accès.
Un tableau de bord est couvert par un octroi parce qu'il se trouve dans le dossier accordé. Déplacez-le vers un autre dossier et il est désormais gouverné par les octrois de ce dossier-là. Il n'y a pas d'octroi par tableau de bord à perdre de vue, ce qui est le bon côté de cette conception ; l'autre côté est que le dossier est la plus petite unité que vous pouvez accorder, donc un dossier que vous partagez est un dossier que vous partagez entièrement.
Ce n'est pas un plafond sur la finesse d'un partage
La phrase ci-dessus parle de l'autorisation, pas du partage. Un dossier est la plus petite chose que vous pouvez nommer dans une autorisation — et c'est vous qui décidez de son contenu. Pour partager exactement un tableau de bord, donnez-lui un dossier à lui seul, ce qui fait trois appels que votre agent possède déjà :
create_folder(name="Q3 board pack")
move_to_folder(resource_type="viz", resource_id=<vizId>, folder_id=<newFolderId>)
grant_access(folder_id=<newFolderId>, resource_type="viz",
principal_id=<userId>, principal_type="user", permissions=["view"])
resource_type vaut dataset ou viz pour le déplacement, principal_type vaut user ou
group, et access_type vaut allow par défaut — deny est l'autre valeur, et elle
l'emporte sur toute autorisation.
Ces trois appels sont des outils MCP, et le MCP est la voie prise en charge pour cela. Les
routes d'access-rights sont volontairement absentes du SDK publié et de la référence d'API
— c'est de la plomberie de contrôle d'accès dont la forme n'est pas engagée, donc aucune
méthode de client générée n'existe pour elles et aucune ne devrait être utilisée.
create_folder est celui des trois qui a un équivalent SDK, client.folders.create.
Déplacer un tableau de bord change quelles autorisations le régissent et rien d'autre — il garde son id, et son lien n'est pas affecté. Donc « le partage par tableau de bord est impossible » est la mauvaise conclusion à tirer de la règle du dossier : c'est trois appels, et le dossier est la manière de l'exprimer.
Les deux moitiés d'une autorisation de tableau de bord
Un tableau de bord et les données en dessous sont régis par des types de ressource
distincts, donc un partage qui n'en nomme qu'un est à moitié fait. viz:view permet à
quelqu'un d'ouvrir le tableau de bord ; dataset:view est ce qui permet à ses tuiles de
renvoyer des chiffres. La section suivante explique pourquoi ce n'est pas un détail qu'on
peut remettre à plus tard.
Comment une décision est réellement prise
Dans l'ordre. La première règle qui correspond l'emporte :
- Admin → autoriser. Sans condition, avant même la lecture d'un octroi.
- Le principal interne
system→ autoriser. C'est l'identité propre de worker de la plateforme (jobs d'actualisation, exécutions planifiées) ; ce n'est pas un compte sur lequel vous pouvez vous connecter. - Propriété → autoriser. L'utilisateur propriétaire de la ligne peut toujours la lire et la modifier.
- Sinon, les octrois de dossier décident, et un seul deny l'emporte sur tous les allow.
Le principe deny-l'emporte mérite d'être précisé, car il est plus étroit qu'il n'y paraît. Un deny retire un dossier de l'ensemble qui vous a été accordé — il ne soustrait pas des règles 1 à 3. Vous ne pouvez pas refuser un admin, et vous ne pouvez pas refuser quelqu'un l'accès à une ligne dont il est propriétaire. Si votre modèle mental est « deny est l'arrêt d'urgence », ce n'est pas le cas : c'est une soustraction à l'intérieur de la seule couche d'octroi.
Deux comportements qui vous mordront
Les octrois ne se propagent pas dans les sous-dossiers. La vérification est un test
d'appartenance à un ensemble plat sur le folder_id propre de la ressource. Les dossiers
ont bien une relation parent/enfant, et l'interface la dessine comme un arbre — mais aucun
code d'autorisation ne la parcourt. Un octroi sur Marketing n'atteint pas
Marketing / T3. Accordez chaque dossier que vous voulez accorder.
Un deny sur create ne faisait autrefois rien — corrigé le 2026-09-08. La vérification
de permission de création ne lisait que les lignes allow ; le droit de bootstrap d'un
créateur à créer à la racine du compte ou dans un dossier dont il est propriétaire, et une
ligne allow explicite ailleurs, contournaient tous deux entièrement un deny. Une ligne
explicite create / deny d'un admin l'emporte désormais sur les deux — la même règle
« un seul deny l'emporte sur tous les allow » que le reste de cette page décrit pour
view/edit/delete. Les admins restent inrefusables, à l'image de toute autre permission ici.
Qui peut modifier un octroi
Un admin du compte, ou le propriétaire du dossier sur lequel porte l'octroi. La lecture des octrois est ouverte à tout membre du compte : l'état de partage est affiché dans l'application sur des pages que tout membre peut ouvrir, la lecture est donc délibérément non verrouillée. Traitez la liste des octrois elle-même comme une information visible par tout le compte.
Ce qu'un octroi peut nommer
Deux types de ressources peuvent faire l'objet d'un octroi : viz (tableaux de bord) et
dataset. Construisez votre modèle de gouvernance avec ces deux-là, rangés dans des
dossiers : c'est la forme que le modèle applique, et la seule que la boîte de dialogue
propose. POST /v1/access-rights refuse tout autre type de ressource avec un 400, au
moment où vous l'écrivez plutôt qu'au moment où quelqu'un compte dessus.
Trois choses sont gouvernées par autre chose qu'un octroi, et savoir laquelle par quoi est tout l'objet de cette section :
| Ressource | Ce qui la gouverne |
|---|---|
| Dossier | L'appartenance au compte pour la lecture ; le rôle pour les mutations. Un dossier est le conteneur qu'un octroi nomme, pas une chose contre laquelle un octroi s'écrit. |
| Planification d'actualisation | La propriété, plus la frontière du compte. |
| Question | Propriétaire uniquement. Une question ne peut être partagée par aucun mécanisme. |
| Tâche | L'ACL du tableau de bord auquel elle appartient. |
| Connexion | Son propriétaire et les administrateurs du compte. Tout Créateur peut en créer une. |
Une liste vide vous dit de quel genre de vide il s'agit.
GET /v1/access-rights/{folder}/{type} porte un drapeau enforced : total: 0 pour un type
non octroyable signifie aucun droit de ce type ne peut exister où que ce soit, tandis que
total: 0 pour viz signifie personne n'en détient. Les lignes écrites avant le refus
restent listées, et restent supprimables, avec enforced: false à côté d'elles.
Partager un tableau de bord sans partager ses données ne fonctionne pas
C'est la question à laquelle le modèle ne répond pas comme la plupart des gens s'y
attendent, elle mérite donc une phrase nette : viz:view seul ne donne à personne un
tableau de bord qui fonctionne.
Les tuiles d'un tableau de bord vont chercher leurs chiffres avec POST /v1/query, et
cette route autorise chaque jeu de données participant via l'ACL des jeux de données,
en mode refuser-plutôt-qu'écarter : un seul jeu de données que le lecteur ne peut pas lire
rejette la requête entière avec 403 query_dataset_forbidden. Elle n'est jamais exécutée
sur le sous-ensemble qu'il a le droit de lire. Ainsi, « que le stagiaire voie ce tableau
de bord mais pas les données derrière » produit un tableau de bord dont les tuiles échouent,
pas un tableau de bord dont les chiffres sont masqués.
Accordez aussi dataset:view sur le dossier qui contient les jeux de données — viz:view et
dataset:view vont par paire, et une autorisation qui ne porte que la première produit un
tableau de bord dont chaque tuile renvoie 403. Si les jeux de données ne doivent pas être
partagés en bloc, donnez-leur un dossier à eux comme le fait la recette ci-dessus. Ou, si le
but est vraiment de montrer un résultat sans livrer les données sous-jacentes, publiez le
tableau de bord ou intégrez-le. Ces deux voies s'exécutent sous l'identité de service de
la plateforme et ne consultent jamais cette ACL, ce qui est précisément pourquoi elles
peuvent montrer un chiffre à quelqu'un qui n'a pas le droit de l'interroger.
Vérifier votre propre travail
Chaque champ ci-dessus décrit une règle ; aucun ne vous dit ce dont dispose une personne
donnée au bout du compte, et cela ne se lit pas sur la liste des octrois. Les droits se
composent — appartenances aux groupes, un deny qui l'emporte sur tous les allow y compris
un allow hérité d'un groupe, la séparation par type de ressource, et le contrôle de rôle
distinct sur les mutations — en un résultat qu'aucune liste ne montre. Le deny hérité est
le cas qui compte : il n'apparaît jamais dans la liste des allow que vous lisez, si bien que
« pourquoi cette personne ne voit-elle pas ceci ? » est sans réponse à partir de la seule liste.
GET /v1/access-rights/{folder}/effective?userId= le résout. Pour chaque couple (type de
ressource, permission), il renvoie ce que décide l'ACL, quelle règle a décidé, si le rôle
de la personne le permet, si le type est appliqué, le produit de tout cela — et la ligne de
droit d'accès qui a produit le verdict, c'est-à-dire l'identifiant à révoquer quand la
réponse est un deny. Votre agent dispose de la même chose sous le nom
describe_effective_access.
Il répond uniquement à la question de l'ACL, et le dit dans une liste caveats présente sur
chaque réponse : il ne peut pas voir les lignes que la personne possède (la propriété est
au niveau de la ligne et l'emporte sur l'ACL du dossier), il ne sait rien des voies de
publication ou d'intégration, et — comme expliqué ci-dessus — un tableau de bord qui
fonctionne exige aussi des droits sur ses jeux de données. Vous pouvez toujours interroger
vos propres droits ; interroger ceux de quelqu'un d'autre exige la même qualité que modifier
l'ACL : un admin du compte ou le propriétaire du dossier.
Y accéder depuis le code
| Surface | Dans les SDKs ? |
|---|---|
| Groupes (création, liste, mise à jour, adhésion) | Oui — client.groups.* |
| Utilisateurs (liste, mise à jour, rôle, adhésion à un groupe) | Oui — client.users.* |
| Compte | Oui — client.accounts.* |
| Droits d'accès (les octrois eux-mêmes) | Non |
Les droits d'accès sont exclus de la surface générée du SDK, il n'y a donc pas de
client.accessRights ni d'entrée pour eux dans la
Référence API. Les routes existent et acceptent une
clé API — elles ne font simplement pas partie du contrat de client publié et versionné, et
nous préférons vous le dire plutôt que de vous laisser construire contre une forme sur
laquelle nous ne nous sommes pas engagés. Gérez les octrois dans l'application, ou depuis
votre agent : le serveur MCP expose des outils d'octroi, de révocation et de liste des
droits d'accès qui appellent les mêmes routes sous votre propre identité, et héritent de la
même règle admin-ou-propriétaire-du-dossier.
Filtrage au niveau des lignes : la voie d'intégration, et seulement elle
La sécurité au niveau des lignes est réelle dans Vibedasher, mais elle vit à un seul endroit : les tableaux de bord intégrés derrière un jeton signé.
Quand votre serveur génère un jeton d'intégration, il peut y attacher des filtres. Sur
chaque requête, la plateforme réécrit chaque référence de jeu de données en une sous-requête
filtrée, de sorte que le visionneur ne peut physiquement pas lire des lignes en dehors de sa
tranche. Les filtres proviennent exclusivement du jeton vérifié — jamais du corps de la
requête — donc un visionneur ne peut pas élargir sa propre portée en modifiant une requête.
Un filtre nommant une colonne qui n'existe dans aucun schéma de jeu de données est rejeté
avec un 403 plutôt qu'ignoré silencieusement, car un filtre qui ne fait silencieusement
rien est une brèche.
Trois limites à prévoir :
- Cela ne s'applique qu'à la voie d'intégration. La voie de requête du SDK n'a aucune injection de filtre obligatoire : une clé API est un appelant côté serveur de confiance, et scoper ses propres utilisateurs est votre travail, pas celui de la plateforme.
- L'ACL de dossier n'est pas au niveau des lignes. Elle décide quels tableaux de bord et jeux de données existent pour vous, pas quelles lignes à l'intérieur vous pouvez lire. Ce sont des questions différentes et Vibedasher y répond avec des mécanismes différents.
- Un tableau de bord qui exécute ses requêtes dans le navigateur ne peut pas être
restreint du tout — c'est voulu. C'est le point à trancher avant de construire, pas
après. Un tableau de bord auto-hébergé sur la voie sans identifiants — l'arbre éjecté dont
le
dataLanevautwasm, ou tout ce que vous avez construit vous-même au-dessus d'un extrait Parquet ou JSON — télécharge les données et exécute chaque requête dans le navigateur du visiteur. L'extrait est adressé par jeu de données et instantané, pas par requête : il contient donc toutes les lignes de ce jeu de données, et chaque visiteur reçoit le même fichier. Il ne reste aucun serveur sur le chemin pour le restreindre : un filtre écrit dans la page change ce qui est dessiné, jamais ce qui a été livré.
Tout l'intérêt de cette voie est qu'il n'y a pas de clé dans le bundle, pas de jeton à générer et pas de backend à faire tourner de votre côté — et le prix, c'est exactement la même propriété vue de l'autre côté : rien ne peut donner moins de lignes à un visionneur qu'à un autre. Ce n'est pas un manque qui sera comblé ; c'est ce que signifie mettre les données dans le navigateur.
Si certains visionneurs doivent voir moins de lignes, gardez la requête sur un serveur. Deux montages le font, et tous deux gardent l'identifiant hors de la page : votre propre backend génère un jeton d'intégration de courte durée portant le filtre (la plateforme ajoute ce prédicat à chaque requête qu'elle autorise, avant toute jointure ou agrégation, de sorte que la page ne peut pas l'élargir), ou bien du code serveur de confiance interroge avec une clé API et renvoie des lignes que vous avez déjà restreintes. Choisir d'abord la voie rapide, puis la « restreindre » ensuite avec un filtre dans la page, produit un tableau de bord qui a l'air correct, ne lève aucune erreur, et livre toutes les lignes.
Voir Intégration pour comment un jeton est généré et présenté.
Authentification à deux facteurs
E-mail + mot de passe est la seule entrée de Vibedasher qui repose sur un secret unique. Les
connexions Google et LinkedIn portent le second facteur de leur fournisseur ; les clés API et
le jeton CLI de vibedasher login ne sont pas des connexions. Le second facteur est donc
opt-in, par utilisateur, sur la connexion par mot de passe uniquement : un code TOTP à six
chiffres issu de n'importe quelle application d'authentification (Google Authenticator,
1Password, Authy…), activé depuis Paramètres → Authentification à deux facteurs en
scannant un QR code et en confirmant un code. L'activation vous remet huit codes de
récupération à usage unique, affichés une seule fois — un téléphone perdu se connecte avec
l'un d'eux.
Ce que cela change à la connexion : après un mot de passe correct, le navigateur ne reçoit
aucune session — il reçoit un défi de cinq minutes, à usage unique, que seule l'étape du
code peut échanger. L'API ne voit jamais ce défi comme un identifiant : il porte une audience
que l'API refuse d'emblée, il ne peut donc ni être rejoué contre api.*.vibedasher.com ni
glissé dans le cookie de session. Cinq codes erronés verrouillent le défi pendant quinze
minutes ; un code n'est valable qu'une fois.
Trois limites à connaître :
- L'inscription est individuelle. Chaque membre l'active pour son propre compte ; ce n'est pas quelque chose qu'un administrateur enclenche pour tout le monde.
- Les connexions OAuth ne sont jamais soumises au défi, volontairement — ajouter notre facteur par-dessus celui du fournisseur, c'est de la friction sans sécurité.
- Le déblocage est une action d'administrateur. Un membre sans téléphone ni code de récupération s'adresse à un administrateur du compte, qui réinitialise son second facteur depuis la page du membre (l'action est journalisée). Un propriétaire qui est le seul administrateur, sans code de récupération, passe par le support.
Une configuration pratique
Pour une équipe de dix personnes avec quelques jeux de données sensibles :
- Désignez comme admins les une ou deux personnes qui administrent le compte. Rappelez-vous qu'elles sont ingouvernables par les octrois — c'est le principe du rôle, et la raison de garder la liste courte.
- Tous ceux qui construisent sont des créateurs ; tous ceux qui ne font que lire sont des visionneurs. Le rôle visionneur est un refus ferme sur toute mutation, c'est donc une vraie frontière, pas une convention d'interface.
- Placez les tableaux de bord et jeux de données dans des dossiers qui correspondent à la façon dont vous voulez les partager — un dossier par audience, pas par sujet. Le dossier est l'octroi.
- Accordez à des groupes, pas à des personnes. L'octroi survit alors à l'arrivée ou au départ de quelqu'un.
- Ne créez pas de dossiers imbriqués en attendant que l'octroi du parent les atteigne. Accordez chacun d'eux.
- Si un tableau de bord doit aller à des personnes en dehors du compte, publiez-le et intégrez-le avec un jeton signé portant leurs filtres de ligne — ne leur donnez pas de compte.