Werkplaats / De werkplaats
In jouw eigen tempo
← AI-security & red teaming
MODULE 02 · LES 05

Geef een agent alleen de rechten voor zijn taak

Vergelijk exacte tool/doelparen met de benodigde taakrechten en bewijs welke normale aanvragen blijven werken.

90 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je berekent benodigde, behouden, overtollige en ontbrekende rechten.
  • Je onderscheidt aangeboden rechten van bevoegdheid voor de huidige taak.

Je assistent moet een kaart lezen en de kleur aanpassen. Waarom zou dezelfde taak ook mogen publiceren? Een onschuldige fout in een modelvoorstel krijgt grotere gevolgen wanneer de applicatie meer macht aanbiedt dan het werk nodig heeft.

Minimaal benodigde rechten (least privilege) begint daarom bij de gewone taak. We gebruiken alleen eigen toolnamen en kaart-ids als gegevens; geen functie op deze pagina bedient een echte tool. De vertrouwde applicatie bepaalt de taakbehoefte voordat zij modelvoorstellen beoordeelt.

1. Begin bij de taak, niet bij het voorstel

Stel je een sleutelbos voor. Voor één klus heb je twee specifieke sleutels nodig; een tweede vermelding van dezelfde deur vraagt geen extra bevoegdheid. In onze oefening is een sleutel het volledige paar van een tool en een doel.

Een leesrecht voor kaart-a is dus niet hetzelfde als een leesrecht voor kaart-b. Ook kleuren en lezen van dezelfde kaart zijn verschillende paren. Door die twee onderdelen samen te bewaren, voorkom je dat een losse lijst toolnamen ongemerkt toegang tot alle kaarten geeft.

T is de vertrouwde lijst taakstappen en R de verzameling unieke benodigde paren. Unique betekent hier ontdubbelen, zonder toolnamen of doelen te veranderen. De resultaatlijst sorteren we voor een vaste, controleerbare weergave.

R=unique(T)

Met de hand bevat de taak lezen, kleuren en nogmaals lezen. Je vindt twee verschillende paren, niet drie. Kleuren staat alfabetisch vóór lezen, maar die sorteervolgorde verandert de gewone taak niet.

steps = [
    dict(tool="lezen",
         target="kaart-a"),
    dict(tool="kleuren",
         target="kaart-a"),
    dict(tool="lezen",
         target="kaart-a"),
]
needed = required_permissions(steps)
print(len(needed))
print(needed[0]["tool"])
# 2
# kleuren
01
CHECKPOINTLeid benodigde paren uit de vertrouwde taak af

2. Onderscheid te veel van te weinig

Nu vergelijken we de benodigde sleutels met de aangeboden sleutelbos. Een ontbrekend recht verhindert gewoon werk; een extra recht maakt onbedoeld werk mogelijk. Die twee problemen vragen verschillende maatregelen.

G is de aangeboden verzameling. K bewaart wat zowel aangeboden als nodig is, E bevat het overschot en M de ontbrekende paren. Het doorsnedeteken ∩ betekent beide verzamelingen; het verschilteken ∖ betekent links zonder rechts.

K=G∩RE=G∖RM=R∖G

Voeg publiceren toe aan de twee benodigde rechten. Dan blijven twee paren bruikbaar, is één paar te ruim en ontbreekt niets. We trekken geen recht bij een echte dienst in; de uitkomst beschrijft wat je in het ontwerp moet aanpassen.

extra = dict(tool="publiceren",
             target="kaart-a")
granted = needed + [extra]
report = permission_diff(
    granted, needed)
print(len(report["kept"]))
print(len(report["excess"]))
print(len(report["missing"]))
# 2
# 1
# 0
02
CHECKPOINTOnderscheid noodzakelijk, te ruim en ontbrekend
VERKEN HET ZELFWelke rechten heeft deze taak nodig?

De vaste taak bepaalt wat nodig is. Verander de aangeboden rechten en vergelijk dienstmacht met toestemming voor deze specifieke taak.

Aangeboden rechten van de dienst

Vaste taakstappen

  • lezen → kaart-a
  • kleuren → kaart-a

Unieke benodigde rechten

  • kleuren → kaart-a
  • lezen → kaart-a

Behouden

  • kleuren → kaart-a
  • lezen → kaart-a

Te ruim

  • kleuren → kaart-b
  • lezen → kaart-b
  • publiceren → kaart-a
  • publiceren → kaart-b

Ontbrekend

Geen.

Dezelfde aanvragen, twee beslisregels

lezen → kaart-a

Alleen dienstrecht: toegestaan

Ook taakgebonden: toegestaan

Toegekend én nodig voor deze taak.

lezen → kaart-b

Alleen dienstrecht: toegestaan

Ook taakgebonden: geweigerd

Dit recht valt buiten de vaste taak.

kleuren → kaart-a

Alleen dienstrecht: toegestaan

Ook taakgebonden: toegestaan

Toegekend én nodig voor deze taak.

kleuren → kaart-b

Alleen dienstrecht: toegestaan

Ook taakgebonden: geweigerd

Dit recht valt buiten de vaste taak.

publiceren → kaart-a

Alleen dienstrecht: toegestaan

Ook taakgebonden: geweigerd

Dit recht valt buiten de vaste taak.

publiceren → kaart-b

Alleen dienstrecht: toegestaan

Ook taakgebonden: geweigerd

Dit recht valt buiten de vaste taak.

lezen → kaart-a

Alleen dienstrecht: toegestaan

Ook taakgebonden: toegestaan

Toegekend én nodig voor deze taak.

Een ontbrekend recht maakt de normale taak onmogelijk. Een overtollig recht geeft de dienst meer macht, maar wordt door de taakgebonden regel geweigerd. Een modelvoorstel kan de vaste taak niet uitbreiden.

Alle kaarten, rechten, routes en kosten zijn eigen oefengegevens. Deze oefening voert geen externe tools uit en verstuurt geen gegevens.

3. Controleer ieder specifiek verzoek

Een modelvoorstel kan nog steeds publiceren vragen. De naïeve controle ziet alleen dat dit in G voorkomt. De verdedigde controle vereist ook lidmaatschap van de apart vertrouwde taakbehoefte R.

A is de toestemmingsbeslissing voor paar p. Beide voorwaarden moeten waar zijn; een onbekend paar wordt door geen van beide kanten toegevoegd. De uitvoer blijft een beslissing over gegevens, geen uitgevoerde handeling.

A(p)=(p∈G)∧(p∈R)
asks = [steps[0], extra]
answers = compare_authority(
    asks, granted, needed)
print(answers[0]["guarded_allowed"])
print(answers[1]["naive_allowed"])
print(answers[1]["guarded_allowed"])
print(answers[1]["reason"])
# True
# True
# False
# outside-task
03
CHECKPOINTVergelijk brede macht met taakgebonden toestemming

Een veelgemaakte fout is alle rechten weghalen en dat een geslaagde verdediging noemen. De gewone leestaak moet beschikbaar blijven. Anders heb je de assistent uitgeschakeld in plaats van zijn macht aan zijn werk aangepast.

denied = compare_authority(
    [steps[0]], [], needed)
print(denied[0]["guarded_allowed"])
print(denied[0]["reason"])
# False
# not-granted

De voorbeelden gebruiken volledige paren en vaste sets. Een echt systeem moet die grenzen ook afdwingen bij de onderliggende dienst, waar een actie plaatsvindt. Alleen een kleinere toolbeschrijving aan het model tonen verleent die bescherming niet.

Wat je nu kunt

  • Je leidt rechten af uit een afzonderlijk vertrouwde taak.
  • Je ziet noodzakelijk, te ruim en ontbrekend als verschillende uitkomsten.
  • Je begrenst ieder verzoek zonder de gewone taak weg te nemen.

Hierna onderscheiden we de macht van de assistent van de bevoegdheid van de persoon namens wie hij handelt.

Bronnen

Gecontroleerd op 30 oktober 2026: NIST over least privilege en OWASP 2026 LLM03 over te veel macht en onafhankelijke autorisatie. Sleutelparen, taakstappen en beslissingen zijn eigen oefenmateriaal.

DE VOLGENDE STAPBind de macht van een assistent aan de opdrachtgever
JOUW EXPERIMENT

Probeer het zelf

Python
main.py

Python start zodra jij code uitvoert.

UITVOER

Je uitvoer verschijnt hier.
Alles rekent in je eigen browser.

Jouw checkpoints0 / 3
01
Leid benodigde paren uit de vertrouwde taak af

Schrijf required_permissions(steps). Tool/doelparen zijn exact list/tuple met 0–24 exacte dicts met uitsluitend exact-str keys tool/target en geldige id-waarden. Een id is exact str volgens ASCII [a-z][a-z0-9-]{0,31}. Steps mag herhaalde paren bevatten. Geef een nieuwe lijst met nieuwe dicts tool/target van alle unieke paren, gesorteerd op de volledige (tool,target)-tuple. Dit zijn behoeften uit een vertrouwde gewone taak, niet uit een modelvoorstel. Alle invoer valideren, ook op geweigerde of later niet uitgevoerde takken. Exacte Python-builtins zonder omzetting of normalisatie; ongeldige invoer geeft ValueError. Behoud alle invoer, volgorde, readonlyflags en Python-/NumPy-RNG, ook bij fouten. Alle mutabele uitvoercontainers zijn nieuw, delen geen containers met invoer, elkaar of herhaalde resultaten. De validatiehelpers zijn gegeven. Iedere functie werkt zelfstandig wanneer alle overige leerlingfuncties nog TODO zijn. Geen echte tool, dienst of netwerkactie uitvoeren.

Een hint

Ontdubbel de volledige paren; sorteer pas daarna.

02
Onderscheid noodzakelijk, te ruim en ontbrekend

Schrijf permission_diff(granted,required). Tool/doelparen zijn exact list/tuple met 0–24 exacte dicts met uitsluitend exact-str keys tool/target en geldige id-waarden. Een id is exact str volgens ASCII [a-z][a-z0-9-]{0,31}. Granted en required bevatten elk unieke paren. Geef een nieuw exact dict kept/excess/missing met elk een nieuwe gesorteerde paarlijst. Kept=granted∩required; excess=granted∖required; missing=required∖granted. Een ander tool of doel is een ander paar. Alle invoer valideren, ook op geweigerde of later niet uitgevoerde takken. Exacte Python-builtins zonder omzetting of normalisatie; ongeldige invoer geeft ValueError. Behoud alle invoer, volgorde, readonlyflags en Python-/NumPy-RNG, ook bij fouten. Alle mutabele uitvoercontainers zijn nieuw, delen geen containers met invoer, elkaar of herhaalde resultaten. De validatiehelpers zijn gegeven. Iedere functie werkt zelfstandig wanneer alle overige leerlingfuncties nog TODO zijn. Geen echte tool, dienst of netwerkactie uitvoeren.

Een hint

Vergelijk aangeboden macht met de apart bepaalde taakbehoefte.

03
Vergelijk brede macht met taakgebonden toestemming

Schrijf compare_authority(requests,granted,required). Tool/doelparen zijn exact list/tuple met 0–24 exacte dicts met uitsluitend exact-str keys tool/target en geldige id-waarden. Een id is exact str volgens ASCII [a-z][a-z0-9-]{0,31}. Requests mag paren herhalen; granted/required elk uniek. Geef per aanvraag, in dezelfde volgorde en met herhalingen, een nieuw exact dict tool/target/naive_allowed/guarded_allowed/reason. Beide allowed-velden zijn exacte Python-bools. Naive toetst alleen granted; guarded toetst granted én required. Redenprioriteit: not-granted, anders outside-task, anders allowed. Aanvragen breiden geen rechten uit. Alle invoer valideren, ook op geweigerde of later niet uitgevoerde takken. Exacte Python-builtins zonder omzetting of normalisatie; ongeldige invoer geeft ValueError. Behoud alle invoer, volgorde, readonlyflags en Python-/NumPy-RNG, ook bij fouten. Alle mutabele uitvoercontainers zijn nieuw, delen geen containers met invoer, elkaar of herhaalde resultaten. De validatiehelpers zijn gegeven. Iedere functie werkt zelfstandig wanneer alle overige leerlingfuncties nog TODO zijn. Geen echte tool, dienst of netwerkactie uitvoeren.

Een hint

Houd beschikbaar en nodig apart; beide voorwaarden moeten gelden.

Vastgelopen? Bekijk de voorbeeldoplossing
import re
_IDENTIFIER = re.compile('[a-z][a-z0-9-]{0,31}', re.ASCII)

# Jouw functies

def required_permissions(steps):
    """Deduplicate trusted ordinary-task requirements, preserving exact pairs."""
    pairs = _permissions(steps, False)
    return _rows(sorted(set(pairs)))

def permission_diff(granted, required):
    """Separate necessary, excess and missing authority without granting any."""
    available = set(_permissions(granted, True))
    needed = set(_permissions(required, True))
    return {'kept': _rows(sorted(available & needed)), 'excess': _rows(sorted(available - needed)), 'missing': _rows(sorted(needed - available))}

def compare_authority(requests, granted, required):
    """Compare broad service authority with a separately bounded task scope."""
    proposals = _permissions(requests, False)
    available = set(_permissions(granted, True))
    needed = set(_permissions(required, True))
    result = []
    for tool, target in proposals:
        pair = (tool, target)
        naive = pair in available
        guarded = naive and pair in needed
        reason = 'not-granted' if not naive else 'outside-task' if not guarded else 'allowed'
        result.append({'tool': tool, 'target': target, 'naive_allowed': naive, 'guarded_allowed': guarded, 'reason': reason})
    return result

# Gegeven validatiehelpers

def _identifier(value):
    if type(value) is not str or _IDENTIFIER.fullmatch(value) is None:
        raise ValueError('Een id moet een exacte ASCII-id zijn')
    return value

def _permissions(value, unique):
    if type(value) not in (list, tuple) or len(value) > 24:
        raise ValueError('Geef maximaal 24 tool/doelparen')
    pairs = []
    for row in value:
        if type(row) is not dict or any((type(key) is not str for key in row)) or set(row) != {'tool', 'target'}:
            raise ValueError('Een paar heeft precies tool en target')
        pair = (_identifier(row['tool']), _identifier(row['target']))
        if unique and pair in pairs:
            raise ValueError('Bevoegdheidsparen zijn uniek')
        pairs.append(pair)
    return pairs

def _rows(pairs):
    return [{'tool': tool, 'target': target} for tool, target in pairs]

Voer de code uit en verklaar voor jezelf waarom elke stap werkt.