- rq_c5c.py: TolariaClient save_token/delete_token DI, _require_token fail-closed, write()/delete() senden Bearer (SAVE/DELETE), read()/list() ohne Credential - rq_c5_cli.py: _delete_executor liest nur DELETE-Credential (Least Privilege) - test_c5c.py: write()-Tests injizieren synthetisches SAVE-Token - test_c5_auth3a.py: isolierte AUTH.3A-Testsuite (19 Tests, Fake/Mock Tolaria) - auth3a-sensitivity.sh: 8 Sensitivitaets-Mutationen (A-H) -> ROT - AUTH3A_HUMAN_APPROVAL_AUTHENTICITY_GAP.md: Gap dokumentiert (OPEN, nicht repariert) Keine echten Tokens. Keine ENV-Mutation. Kein Deployment. Keine produktive Auth-Aktivierung.
133 lines
4.6 KiB
Bash
133 lines
4.6 KiB
Bash
#!/usr/bin/env bash
|
|
# AUTH.3A PHASE 11 — Test-Sensitivity: Beweise ROT bei gebrochener Auth-Semantik.
|
|
#
|
|
# Mutiert rq_c5c.py temporaer (A-H) und prueft, dass die AUTH.3A-Testsuite
|
|
# ROT wird. Keine hardcodierten PASS-Werte. Keine Produktion.
|
|
#
|
|
# A. fehlendes Token sendet Request trotzdem
|
|
# B. Authorization Header entfernt
|
|
# C. SAVE Caller laedt DELETE Token
|
|
# D. DeleteExecutor laedt SAVE Token
|
|
# E. Delete ohne Human Approval erlaubt
|
|
# F. Token wird geloggt
|
|
# G. Retry verliert Authorization
|
|
# H. Replay faellt auf unauthenticated zurueck
|
|
set -u
|
|
cd "$(dirname "$0")"
|
|
TARGET="rq_c5c.py"
|
|
TARGET2="rq_c5_delete.py"
|
|
TEST="test_c5_auth3a.py"
|
|
PY="python3 -m unittest ${TEST%.py}"
|
|
|
|
fail=0
|
|
run_mutation() {
|
|
local name="$1"; shift
|
|
local expect_red="$1"; shift
|
|
# Mutation anwenden
|
|
"$@" || { echo "MUTATION-APPLY-FAIL: $name"; fail=1; return; }
|
|
# Tests ausfuehren
|
|
local out
|
|
out=$($PY 2>&1)
|
|
local rc=$?
|
|
# Mutation zurueckrollen (aus .bak, NICHT git checkout — AUTH.3A-Aenderungen sind uncommitted)
|
|
cp "$TARGET.bak" "$TARGET"
|
|
cp "$TARGET2.bak" "$TARGET2"
|
|
if [ "$expect_red" = "RED" ]; then
|
|
if [ $rc -ne 0 ]; then
|
|
echo "OK [$name] -> ROT (rc=$rc)"
|
|
else
|
|
echo "FAIL[$name] -> GRUEN (sollte ROT sein)"
|
|
fail=1
|
|
fi
|
|
else
|
|
if [ $rc -eq 0 ]; then
|
|
echo "OK [$name] -> GRUEN"
|
|
else
|
|
echo "FAIL[$name] -> ROT (sollte GRUEN sein)"
|
|
fail=1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
cp "$TARGET" "$TARGET.bak"
|
|
cp "$TARGET2" "$TARGET2.bak"
|
|
|
|
# A. fehlendes Token sendet Request trotzdem (entferne _require_token in write)
|
|
run_mutation "A-fehlendes-token-sendet-trotzdem" RED \
|
|
python3 - <<'EOF'
|
|
import re
|
|
p="rq_c5c.py"
|
|
s=open(p).read()
|
|
# write(): _require_token entfernen -> sendet ohne Token
|
|
s=s.replace("token = self._require_token(self.save_token, \"SAVE\")","token = self.save_token")
|
|
open(p,"w").write(s)
|
|
EOF
|
|
|
|
# B. Authorization Header entfernt (kein auth_token an _post)
|
|
run_mutation "B-auth-header-entfernt" RED \
|
|
python3 - <<'EOF'
|
|
p="rq_c5c.py"
|
|
s=open(p).read()
|
|
s=s.replace("if auth_token is not None:\n headers[\"Authorization\"] = f\"Bearer {auth_token}\"","pass")
|
|
open(p,"w").write(s)
|
|
EOF
|
|
|
|
# C. SAVE Caller laedt DELETE Token (write nutzt delete_token)
|
|
run_mutation "C-save-laedt-delete-token" RED \
|
|
python3 - <<'EOF'
|
|
p="rq_c5c.py"
|
|
s=open(p).read()
|
|
s=s.replace("token = self._require_token(self.save_token, \"SAVE\")","token = self._require_token(self.delete_token, \"SAVE\")")
|
|
open(p,"w").write(s)
|
|
EOF
|
|
|
|
# D. DeleteExecutor laedt SAVE Token (delete nutzt save_token)
|
|
run_mutation "D-delete-laedt-save-token" RED \
|
|
python3 - <<'EOF'
|
|
p="rq_c5c.py"
|
|
s=open(p).read()
|
|
s=s.replace("token = self._require_token(self.delete_token, \"DELETE\")","token = self._require_token(self.save_token, \"DELETE\")")
|
|
open(p,"w").write(s)
|
|
EOF
|
|
|
|
# E. Delete ohne Human Approval erlaubt (entferne _validate_approval in execute)
|
|
run_mutation "E-delete-ohne-approval-erlaubt" RED \
|
|
python3 - <<'EOF'
|
|
p="rq_c5_delete.py"
|
|
s=open(p).read()
|
|
s=s.replace("approval = self._validate_approval(commit_sha, change)","approval = {'approval_id': 'x', 'workflow_commit': commit_sha, 'object_id': change.get('object_id'), 'path': change.get('path_before') or change.get('path_after')}")
|
|
open(p,"w").write(s)
|
|
EOF
|
|
|
|
# F. Token wird geloggt (repr enthaelt Token)
|
|
run_mutation "F-token-geloggt" RED \
|
|
python3 - <<'EOF'
|
|
p="rq_c5c.py"
|
|
s=open(p).read()
|
|
# Fuege eine __repr__ ein, die den save_token exponiert (nach self.delete_token = delete_token)
|
|
s=s.replace(" self.save_token = save_token\n self.delete_token = delete_token",
|
|
" self.save_token = save_token\n self.delete_token = delete_token\n\n def __repr__(self):\n return f\"TolariaClient(save_token={self.save_token})\"")
|
|
open(p,"w").write(s)
|
|
EOF
|
|
|
|
# G. Retry verliert Authorization (write sendet kein Token beim 2. Aufruf)
|
|
run_mutation "G-retry-verliert-auth" RED \
|
|
python3 - <<'EOF'
|
|
p="rq_c5c.py"
|
|
s=open(p).read()
|
|
s=s.replace("token = self._require_token(self.save_token, \"SAVE\")","token = self._require_token(self.save_token, \"SAVE\") if not hasattr(self,'_retried') else None\n self._retried = True")
|
|
open(p,"w").write(s)
|
|
EOF
|
|
|
|
# H. Replay faellt auf unauthenticated zurueck (delete ohne Token)
|
|
run_mutation "H-replay-unauthenticated" RED \
|
|
python3 - <<'EOF'
|
|
p="rq_c5c.py"
|
|
s=open(p).read()
|
|
s=s.replace("token = self._require_token(self.delete_token, \"DELETE\")","token = None")
|
|
open(p,"w").write(s)
|
|
EOF
|
|
|
|
rm -f "$TARGET.bak" "$TARGET2.bak"
|
|
echo "=== SENSITIVITY RESULT: $([ $fail -eq 0 ] && echo ALL-RED || echo FAILURES) ==="
|
|
exit $fail
|