Merge pull request 'fix(security): production read agent is now actually read-only' (#81) from arcodange/prod-apply into main
This commit was merged in pull request #81.
This commit is contained in:
@@ -229,8 +229,21 @@ def stage_apply(args) -> None:
|
||||
if os.environ.get("ARCO_PROD_CONFIRM") != "I-UNDERSTAND-THIS-WRITES-PROD":
|
||||
die("set ARCO_PROD_CONFIRM=I-UNDERSTAND-THIS-WRITES-PROD to apply to production")
|
||||
|
||||
# The production writer is its OWN user with the narrow `prod-write` scope
|
||||
# (test/scopes.ts) — not the read-only `ai_agent` whose key every read skill
|
||||
# holds. A read credential must never be able to write the ledger, and the
|
||||
# writer must not be lying around in a file a dozen skills load.
|
||||
cfg = env_from(os.path.join(SKILL, "dolibarr", ".env"))
|
||||
base, key = cfg["DOLIBARR_URL"], cfg["DOLIBARR_API_KEY"]
|
||||
base = cfg["DOLIBARR_URL"]
|
||||
key_file = os.path.join(REPO, "test", ".ai_agent_prod_prod_write.key")
|
||||
if not os.path.exists(key_file):
|
||||
die("no production writer key.\n"
|
||||
f" expected: {key_file}\n"
|
||||
" provision it: DOLIBARR_ADDRESS=https://erp.arcodange.lab "
|
||||
"ARCO_ALLOW_PRODUCTION=erp.arcodange.lab "
|
||||
"ARCO_PROD_CONFIRM=I-UNDERSTAND-THIS-WRITES-PROD \\\n"
|
||||
" deno run -A test/provisionAiUser.ts --scope prod-write --env production")
|
||||
key = open(key_file).read().strip()
|
||||
if SANDBOX_HOST in base:
|
||||
die(f"the prod .env points at the sandbox ({base}) — nothing to promote to")
|
||||
print(f"*** PRODUCTION: {base} — approved by {gate['by']} at {gate['at']} ***")
|
||||
|
||||
+73
-17
@@ -41,27 +41,79 @@ export interface Scope {
|
||||
* `provisionAiUser.ts --audit` prints the live labels next to each granted id.
|
||||
*/
|
||||
export const PERMISSION_LABELS: Readonly<Record<number, string>> = {
|
||||
// --- lecture / export (le socle read) ---
|
||||
11: "Lire les factures clients",
|
||||
16: "Exporter les factures clients",
|
||||
21: "Consulter les propositions commerciales",
|
||||
28: "Exporter les propositions commerciales",
|
||||
31: "Lire les comptes bancaires",
|
||||
41: "Lire les projets et les tâches",
|
||||
45: "Exporter les projets",
|
||||
91: "Lire les charges fiscales ou sociales et paiement de TVA",
|
||||
94: "Exporter les charges fiscales ou sociales",
|
||||
111: "Lire les factures fournisseurs",
|
||||
121: "Consulter les tiers",
|
||||
126: "Exporter les tiers",
|
||||
141: "Lire tous les projets et tâches",
|
||||
161: "Lire les contrats/abonnements",
|
||||
167: "Exporter les contrats",
|
||||
251: "Lire les utilisateurs",
|
||||
262: "Étendre l'accès à TOUS les tiers et leurs objets (voir_tous)",
|
||||
281: "Consulter les contacts",
|
||||
358: "Exporter les utilisateurs",
|
||||
531: "Consulter les services",
|
||||
771: "Lire les notes de frais",
|
||||
779: "Exporter les notes de frais",
|
||||
1181: "Consulter les fournisseurs",
|
||||
1182: "Consulter les commandes fournisseurs",
|
||||
1191: "Exporter les commandes fournisseurs",
|
||||
1201: "Récupérer le résultat d'un export",
|
||||
1231: "Lire les factures (et paiements) fournisseurs",
|
||||
1236: "Exporter les factures fournisseur",
|
||||
1321: "Exporter les factures clients, attributs et règlements",
|
||||
2401: "Lire ses propres actions/événements",
|
||||
2411: "Lire les actions/événements des autres",
|
||||
2414: "Exporter les événements des autres",
|
||||
2501: "Lire/récupérer les documents",
|
||||
3201: "Lire les événements archivés et leurs empreintes",
|
||||
50411: "Lire les opérations du Grand livre",
|
||||
|
||||
// --- écriture (jamais dans le scope read) ---
|
||||
12: "Créer/modifier les factures clients",
|
||||
14: "Émettre des paiements sur les factures clients",
|
||||
// 15 = "Supprimer les factures clients" — DELIBERATELY NEVER GRANTED
|
||||
16: "Exporter les factures clients",
|
||||
121: "Consulter les tiers",
|
||||
122: "Créer/modifier les tiers",
|
||||
// 125 = "Supprimer les tiers" — DELIBERATELY NEVER GRANTED
|
||||
126: "Exporter les tiers",
|
||||
241: "Consulter les propositions commerciales",
|
||||
242: "Créer/modifier les propositions commerciales",
|
||||
// 244 = "Supprimer les propositions commerciales" — DELIBERATELY NEVER GRANTED
|
||||
261: "Consulter les produits/services",
|
||||
262: "Créer/modifier les produits/services",
|
||||
31: "Lire les comptes bancaires",
|
||||
22: "Créer/modifier les propositions commerciales",
|
||||
32: "Créer/modifier les comptes bancaires",
|
||||
111: "Lire les factures fournisseurs",
|
||||
251: "Lire les utilisateurs",
|
||||
122: "Créer/modifier les tiers",
|
||||
130: "Créer/modifier les informations de paiement des tiers",
|
||||
282: "Créer/modifier les contacts",
|
||||
1202: "Créer/modifier un export",
|
||||
1232: "Créer les factures fournisseur",
|
||||
2402: "Créer/modifier des actions/événements",
|
||||
2503: "Soumettre ou supprimer des documents",
|
||||
50401: "Lier les produits et factures avec des comptes comptables",
|
||||
|
||||
// --- suppression : JAMAIS accordée par aucun scope ---
|
||||
// 15 Supprimer les factures clients
|
||||
// 27 Supprimer les propositions commerciales
|
||||
// 125 Supprimer les tiers
|
||||
// 2403 Supprimer des actions/événements
|
||||
} as const;
|
||||
|
||||
const READ_ONLY: ReadonlyArray<number> = [11, 111, 121, 126, 241, 261, 31, 251, 16];
|
||||
/**
|
||||
* Le socle de lecture — établi par audit de l'instance de production, pas
|
||||
* deviné : c'est l'ensemble exact des droits de lecture/export dont les skills
|
||||
* se servent aujourd'hui (factures clients et fournisseurs, tiers, contacts,
|
||||
* documents, contrats, projets, banque, grand livre, TVA).
|
||||
*
|
||||
* `262` (voir_tous) est indispensable : sans lui, les endpoints de liste
|
||||
* renvoient des tableaux vides plutôt qu'un 403 — le piège documenté par la
|
||||
* skill `dolibarr`.
|
||||
*/
|
||||
const READ_ONLY: ReadonlyArray<number> = [
|
||||
11, 16, 21, 28, 31, 41, 45, 91, 94, 111, 121, 126, 141, 161, 167, 251, 262,
|
||||
281, 358, 531, 771, 779, 1181, 1182, 1191, 1201, 1231, 1236, 1321, 2401,
|
||||
2411, 2414, 2501, 3201, 50411,
|
||||
];
|
||||
|
||||
export const SCOPES: Readonly<Record<string, Scope>> = {
|
||||
/**
|
||||
@@ -83,7 +135,7 @@ export const SCOPES: Readonly<Record<string, Scope>> = {
|
||||
"sandbox-write": {
|
||||
purpose: "Répétition d'un change-set sur la sandbox (factures, tiers, produits, propositions, règlements)",
|
||||
environments: ["sandbox"],
|
||||
rights: [...READ_ONLY, 12, 122, 262, 242, 14, 32],
|
||||
rights: [...READ_ONLY, 12, 14, 22, 32, 122, 130, 282, 1232, 2503],
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -98,7 +150,11 @@ export const SCOPES: Readonly<Record<string, Scope>> = {
|
||||
"prod-write": {
|
||||
purpose: "Écriture de production, gated — création/modification de factures et rattachement de règlements",
|
||||
environments: ["production"],
|
||||
rights: [...READ_ONLY, 12, 14],
|
||||
// 2503 est nécessaire à builddoc (régénérer le PDF d'une facture modifiée).
|
||||
// Dolibarr le livre en bundle « soumettre OU supprimer » : on ne peut pas
|
||||
// avoir l'un sans l'autre. C'est la seule capacité de suppression du modèle,
|
||||
// et elle est confinée au writer de production, gated par le promote.
|
||||
rights: [...READ_ONLY, 12, 14, 2503],
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user