fix(security): production read agent is now actually read-only #81
@@ -229,8 +229,21 @@ def stage_apply(args) -> None:
|
|||||||
if os.environ.get("ARCO_PROD_CONFIRM") != "I-UNDERSTAND-THIS-WRITES-PROD":
|
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")
|
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"))
|
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:
|
if SANDBOX_HOST in base:
|
||||||
die(f"the prod .env points at the sandbox ({base}) — nothing to promote to")
|
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']} ***")
|
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.
|
* `provisionAiUser.ts --audit` prints the live labels next to each granted id.
|
||||||
*/
|
*/
|
||||||
export const PERMISSION_LABELS: Readonly<Record<number, string>> = {
|
export const PERMISSION_LABELS: Readonly<Record<number, string>> = {
|
||||||
|
// --- lecture / export (le socle read) ---
|
||||||
11: "Lire les factures clients",
|
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",
|
12: "Créer/modifier les factures clients",
|
||||||
14: "Émettre des paiements sur les factures clients",
|
14: "Émettre des paiements sur les factures clients",
|
||||||
// 15 = "Supprimer les factures clients" — DELIBERATELY NEVER GRANTED
|
22: "Créer/modifier les propositions commerciales",
|
||||||
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",
|
|
||||||
32: "Créer/modifier les comptes bancaires",
|
32: "Créer/modifier les comptes bancaires",
|
||||||
111: "Lire les factures fournisseurs",
|
122: "Créer/modifier les tiers",
|
||||||
251: "Lire les utilisateurs",
|
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;
|
} 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>> = {
|
export const SCOPES: Readonly<Record<string, Scope>> = {
|
||||||
/**
|
/**
|
||||||
@@ -83,7 +135,7 @@ export const SCOPES: Readonly<Record<string, Scope>> = {
|
|||||||
"sandbox-write": {
|
"sandbox-write": {
|
||||||
purpose: "Répétition d'un change-set sur la sandbox (factures, tiers, produits, propositions, règlements)",
|
purpose: "Répétition d'un change-set sur la sandbox (factures, tiers, produits, propositions, règlements)",
|
||||||
environments: ["sandbox"],
|
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": {
|
"prod-write": {
|
||||||
purpose: "Écriture de production, gated — création/modification de factures et rattachement de règlements",
|
purpose: "Écriture de production, gated — création/modification de factures et rattachement de règlements",
|
||||||
environments: ["production"],
|
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;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user