KB-CB64

Claude — C2A: Directus bootstrap admin rotation readiness + no-op rollback drill (2026-07-18)

43 min read Revision 1
current-statec2adirectusbootstrap-adminrotation-readinessstatic-tokenrollback-drillno-op-drillsecret-containmentsession-invalidationargon2pg-dump-exfiltrationsecret-manageroff-vps-accessgit-safetyzero-touch2026-07-18C2_ROTATION_READY_AWAITING_OWNER_GO_FOR_C2B

C2A — Directus bootstrap admin: rotation readiness + controlled no-op rollback drill (2026-07-18)

Tác giả: Claude (Opus 4.8). Host: vmi3080463 / 38.242.240.89 (Contabo). Nguồn điều hành: survey rev8 + Wave A1 rev1 + C0 rev1 + C1 rev1 — đọc toàn văn cả bốn, cộng đo live. Mọi giá trị secret = REDACTED. Không password/token/key/hash nào trong báo cáo này. Owner đã cấp GO_C2A_ROLLBACK_DRILL sau khi mọi read-only gate PASS.


1. EXECUTIVE VERDICT

C2_ROTATION_READY_AWAITING_OWNER_GO_FOR_C2B
DIRECTUS_ADMIN_TOPOLOGY   = VERIFIED_SINGLE_BOOTSTRAP_ADMIN
DIRECTUS_AUTH_SAFETY      = PASS
DIRECTUS_STATIC_TOKEN     = AUTH_FULL_ADMIN_PASS
ADMIN_STATIC_TOKEN_EXPOSURE = CURRENT_AND_HISTORICAL_FOUND
ADMIN_SESSION_DEPENDENTS  = IDENTIFIED (+ NO_CRITICAL_MACHINE_DEPENDENCY_FOUND)
DIRECTUS_BOOTSTRAP_REAPPLY= CLEARED_NO_REAPPLY
DIRECTUS_CONSUMER_ROUNDTRIP = PASS
DIRECTUS_SM_READINESS     = NON_AMBIGUOUS
DIRECTUS_ROLLBACK_PATH_B  = PROVEN_BY_STATIC_TOKEN_NOOP_DRILL
OFF_VPS_GCP_ACCESS        = VERIFIED (caveat: cached credential — xem §24)
VPS_SSOT_GIT_SAFETY       = PASS
C2B_SCOPE                 = ROTATE_PASSWORD_AND_STATIC_TOKEN_AS_ONE_ADMIN_CREDENTIAL_SET

🔴 Ba phát hiện mới, chưa từng có trong KB

  1. pg_dump đêm nào cũng mang static token admin rời VPS. backup-to-gdrive.sh L43 chạy docker exec postgres pg_dump -U directus -d directus → gzip → Drive root, không mã hoá. Directus 11.5 lưu static token plaintext trong directus_users.token (đo bằng giá trị: fingerprint token trong DB trùng khít token trong docker/.env). ⇒ mỗi đêm một bản admin token mới lên Drive. Các báo cáo trước chỉ soi .env / TLS key / PG-dump-như-dữ-liệu; chưa ai nối chuỗi "PG dump ⇒ chứa directus_users.token ⇒ admin token bị đẩy off-site".

  2. Bất đối xứng password vs token — hai đường rò khác nhau, cần hai cách vá khác nhau.

    Password Static token
    Lưu trong DB argon2id hash (97 B) 🔴 plaintext
    Trong git history 🔴 2 object (agent-data-repo) 0
    Trong git HEAD 0 (C1 đã gỡ) 0
    Trong PG dump → Drive ✅ không (chỉ hash) 🔴 có, mỗi đêm
    File plaintext hiện hành 21 🔴 37

    ⇒ Rewrite git history (C3) không đụng được token. Rotate token không đụng được password trong git. Phải làm cả hai, và đó là lý do C2B bắt buộc gộp thành một credential set.

  3. dot-apr-execute — cron */5 — đang HỎNG âm thầm. Log 9.2 MB toàn curl: (7) Failed to connect to localhost port 8055. Nó export DIRECTUS_ADMIN_PASSWORD nhưng chưa bao giờ chạm được Directus (Directus không publish port ra host). ⇒ tiền đề "có consumer password chạy mỗi 5 phút" là SAI; không có phụ thuộc auth 5-phút nào. (Đây là lỗi production độc lập, ngoài phạm vi C2A — chỉ ghi nhận.)

Không tuyên bố đã đóng exposure. C2A không đổi password, không rotate token.


2. SOURCES READ

  • claude-post-migration-service-secret-controlplane-reconciliation-survey-2026-07-17.md rev8 (58.569 ký tự, toàn văn)
  • claude-wave-a1-agent-data-image-compose-env-hardening-2026-07-17.md rev1 (41.831 ký tự, toàn văn)
  • claude-c0-stop-git-history-drive-propagation-2026-07-17.md rev1 (18.870 ký tự, toàn văn)
  • claude-c1-remove-directus-admin-hardcode-current-source-2026-07-17.md rev1 (15.480 ký tự, toàn văn)
  • Evidence gốc: source thật của Directus 11.5.1 trong image đang chạy (bootstrap, users service, authentication service, rate-limiter, users controller, CLI), PG read-only, docker inspect, crontab, wrappers, gcloud metadata.

Không dựa vào memory ở bất kỳ điểm nào mâu thuẫn với report/live evidence.

3. OWNER BOOTSTRAP CONTEXT

admin@example.com là bootstrap Administrator đang vận hành. nmhuyen@gmail.com tồn tại nhưng chưa vận hành được. Chưa có bảng nhân sự. C2A không triển khai kiến trúc tương lai nào; không đổi email/role/policy; không tạo admin thứ hai; không đụng nmhuyen@gmail.com.

4. GIT SAFETY GATE = PASS

/opt/incomex                          branch feat/s177-sprint1-round-a  HEAD 2b086472  remote KHÔNG CÓ ⇒ push bất khả thi
/opt/incomex/docker/agent-data-repo   branch main                      HEAD 7f13207f  remote github.com/Huyen1974/agent-data-test  status CLEAN
dirty /opt/incomex: 104 (15 M + 89 ??) == đúng trạng thái C1 POST ⇒ KHÔNG drift
Phân loại: MISSION_CHANGE=0 (báo cáo đi KB, không commit) · VERIFIED_LIVE_BASELINE (docker/.env, protected file)
           · UNRELATED_WORK=104 (giữ nguyên) · SECRET_IN_HEAD=0 · UNKNOWN=0

Bundles (root-only, VPS-only, ngoài Drive/GitHub/KB):

opt-incomex-PRE.bundle       962a2cfec9cbff3bae46050c6c13962a3d2e94f410c7823c270044db10dafa6c
agent-data-repo-PRE.bundle   8d86073409e7786feee8f4fb1a80cd65c71df1d07464ae1da888c61ef30166c5
git bundle verify: cả 2 = "The bundle records a complete history"  ⇒ PASS

🔴 Bundle /opt/incomex chứa password lịch sử trong git object ⇒ root-only /root, đã kiểm: code-backup-to-gdrive.shbackup-to-gdrive.sh0 tham chiếu /root; checkpoint không nằm trong git root nào.

C2A không tạo commit nào. GIT_COMMIT=NO, GIT_PUSH=NO.

5. DIRECTUS VERSION

image      directus/directus:11.5      package.json version = 11.5.1
container  incomex-directus  2245d86d2f6a  StartedAt 2026-05-08T09:13:06Z  Health=healthy  RestartCount=0

⇒ Dùng Directus 11 policies model, không dùng schema role cũ.

6. BOOTSTRAP ADMIN IDENTITY + UNIQUENESS

BOOTSTRAP_ADMIN_EMAIL     = admin@example.com
BOOTSTRAP_ADMIN_ID        = 6abdec55-d911-44df-af96-3cf60b9654af
status=active  provider=default  role=a40a1070-0b62-4a7e-b2c0-bd4bce9d41ac (Administrator)
password=present(argon2id)  token=present  tfa_secret=NULL
BOOTSTRAP_ADMIN_FULL_ACCESS = true
FULL_ADMIN_USER_COUNT       = 1     (trên tổng 13 user)

Chuỗi chứng minh theo đúng model 11 (không suy đoán):

directus_policies : 8 policy, ĐÚNG 1 có admin_access=t  → 8a613123-2538-4943-9d67-b6d261a4789c "Administrator"
directus_access   : policy 8a613123 gắn vào ĐÚNG 1 hàng, role=a40a1070, user=NULL
                    (không có hàng nào gắn admin policy trực tiếp cho user)
directus_users    : ĐÚNG 1 user có role=a40a1070 → admin@example.com
directus_roles    : 9 role, parent=NULL toàn bộ ⇒ không có kế thừa role làm rò admin

ADMIN_TOPOLOGY = VERIFIED_SINGLE_BOOTSTRAP_ADMIN. Không có second independent admin.

7. nmhuyen@gmail.com — METADATA ONLY, KHÔNG ĐỔI

id=ae44e653-df5d-4a3c-bf6e-714485b616d2  status=active  provider=default
role=NULL   policies=none   password=present(argon2id 97)   token=present(32B)   tfa=NULL
last_access = never   (PRE == POST, không đổi)

⇒ Có password static token, nhưng không role, không policy ⇒ token đó xác thực được nhưng không có quyền gì ngoài Public policy. Không phải rollback identity. NMHUYEN_ACCOUNT = UNCHANGED_METADATA_ONLY — C2A chỉ đọc.

8. EFFECTIVE ROLE / POLICY / ADMIN_ACCESS PROOF

Chứng minh hiệu lực thật, không chỉ metadata: gọi API bằng static token.

GET /users/me       200  id=6abdec55…  email=admin@example.com  role.name=Administrator
GET /policies       200  rows=8   admin_policies=['Administrator']
GET /users          200  rows=13  (đọc được TOÀN BỘ user)
GET /roles          200  rows=9
GET /server/info    200  keys=[extensions, project, queryLimit, rateLimit, rateLimitGlobal, version, websocket]

Vì sao 200 ở /policies chỉ có thể nghĩa là admin: directus_permissions1207 hàng nhưng 0 hàng cho directus_policies|directus_access|directus_users|directus_roles ⇒ không tồn tại grant non-admin nào cho các collection này. Thêm: /server/info trả 7 key mở rộng (non-admin chỉ nhận project).

9. TFA STATUS

DIRECTUS_TFA_STATUS = DISABLED
tfa_secret IS NULL cho CẢ 13 user (không riêng admin)

Code path xác nhận (services/authentication.js): if (user.tfa_secret && !options?.otp) throw InvalidOtpErrortfa_secret NULL ⇒ không cần OTP. Không HOLD_ADMIN_TFA_ENABLED_OWNER_ACTION. updateMany chặn sửa tfa_secret thủ công ⇒ drill không thể vô tình đổi TFA.

10. PASSWORD POLICY

directus_settings.auth_password_policy = NULL
Directus container env: KHÔNG có biến AUTH_PASSWORD_POLICY
PASSWORD_POLICY = KNOWN → KHÔNG CÓ CHÍNH SÁCH NÀO ĐƯỢC ÁP

Chứng minh từ source đang chạy (services/users.js checkPasswordPolicy):

const { auth_password_policy: policyRegExString } = await settingsService.readSingleton({...});
if (!policyRegExString) { return; }        // ← NULL ⇒ thoát sớm, không kiểm gì

⇒ Directus chấp nhận mọi password. Không đoán theo tài liệu mặc định.

11. LOGIN-ATTEMPT / LOCKOUT / RATE-LIMIT

directus_settings.auth_login_attempts = 25
RATE_LIMITER_ENABLED = "false" (compose)   ⇒ không rate-limit HTTP chung
loginAttemptsLimiter = createRateLimiter('RATE_LIMITER', { duration: 0 })   ← store mặc định = MEMORY

Ngữ nghĩa thật, đọc từ services/authentication.js:

  • .consume(user.id) chạy mọi lần đăng nhập (kể cả thành công), trước provider.login().
  • duration: 0 ⇒ điểm không bao giờ tự hết hạn.
  • Khi hết điểm: UPDATE directus_users SET status='suspended'khoá tài khoản, không phải chỉ chặn tạm.
  • Nhưng khi login thành công: await loginAttemptsLimiter.set(user.id, 0, 0)reset về 0.

LOCKOUT_POLICY = KNOWN: 25 lần thất bại LIÊN TIẾP ⇒ suspended. Bộ đếm trong RAM, reset khi Directus restart. C2A thực hiện 0 lần login sai (mission cấm; §25 cũng cấm). Rủi ro lockout = 0.

12. STATIC TOKEN — PRESENCE, AUTH, EFFECTIVE ACCESS

STATIC_TOKEN_AUTH              = PASS
STATIC_TOKEN_EFFECTIVE_ACCESS  = FULL_ADMIN
STATIC_TOKEN_ROLLBACK_CANDIDATE= YES
token length = 64 B (giá trị REDACTED)

🔴 Đo bằng GIÁ TRỊ (không tin tên biến), khoá HMAC ngẫu nhiên, giá trị không bao giờ vào argv:

dotenv.DIRECTUS_ADMIN_TOKEN        len=64  fp=X   ┐
agent-data.DIRECTUS_ADMIN_TOKEN    len=64  fp=X   ├ TRÙNG KHÍT
nuxt.NUXT_DIRECTUS_SERVICE_TOKEN   len=64  fp=X   │
directus_users.token (admin)       len=64  fp=X   ┘ ⇒ Directus 11.5 lưu static token PLAINTEXT trong DB
claude-mcp.DIRECTUS_TOKEN          len=32  fp=Y   ⇒ = token của user tac-agent, KHÁC admin

Đính chính quan trọng cho C2B: claude-mcp KHÔNG dùng admin token ⇒ rotate admin token không làm gãy claude-mcp. Consumer thật của admin token: agent-data, nuxt, cron dot-orphan-scanner + dot-misclass-scanner (qua cron-env.sh).

Stateless đã chứng minh: sessions 73 → 73 và last_access không đổi qua toàn bộ probe ⇒ static-token auth không tạo session, không cập nhật last_access.

13. STATIC-TOKEN EXPOSURE SCAN

13.1 Methodology

Giá trị → file tmpfs 0600grep -rlF -f <file>. Giá trị KHÔNG bao giờ vào argv (đúng cách mà survey §18 tự khai đã làm sai). Pattern shred bằng trap EXIT. Chỉ in đường dẫn + số đếm, không in nội dung.

13.2 Verdict

ADMIN_STATIC_TOKEN_EXPOSURE = CURRENT_AND_HISTORICAL_FOUND      (37 file hiện hành)
Vùng Hits Ghi chú
/opt/incomex (trừ backups/.git/node_modules) 18 gồm .env.local, docker/.env, 5 × .env.bak*, data/tac/gate-b-*/token.txt, 9 evidence JSON
/opt/incomex/backups 12 PG dump prod-directus-*.sql (T5–2026) + 2 inspect JSON
/root 6 🔴 /root/.bashrc, 3 PG dump, 2 file backup Wave A1 (hợp lệ)
/etc 1 /etc/incomex/agent-data.env (protected, hợp lệ)
git object (cả 3 repo, full history) 0 ✅ token KHÔNG ở trong git ⇒ không lên GitHub
Live process env 5 pid agent-data (uvicorn+3 python), nuxt (node)

Khớp chéo với survey: survey đo TOKEN = 34 file (30 /opt/incomex + 4 /root). Tôi đo 30 /opt/incomex (khớp chính xác) + 6 /root — chênh đúng 2 filewave-a1-checkpoint/backup/{dotenv,agent-data.env}.PRE tạo sau khi survey chạy. ⇒ nhất quán, giải thích được.

13.3 Hai đường rò đáng lo nhất

  • 🔴 PG dump: backup-to-gdrive.sh L43 pg_dump -U directus -d directus → Drive root, không mã hoá. DB chứa token plaintexttoken rời VPS mỗi đêm. Các dump T5 trên đĩa chứa CHÍNH token hiện hànhtoken chưa từng được xoay kể từ ít nhất 2026-05-16.
  • 🔴 /root/.bashrc:107 export DIRECTUS_ADMIN_TOKEN=… ⇒ mọi shell root tương tác mang token trong env. Đã kiểm thực nghiệm: shell non-interactive (ssh của tôi) KHÔNG có, vì L6 [ -z "$PS1" ] && return chặn trước.

13.4 Vùng CHƯA quét (khai báo trung thực)

- Artifact Drive (56 code-backup/repo + 14 nightly): KHÔNG tải về — tải sẽ tạo thêm bản sao secret trên đĩa.
  Suy luận có cơ sở: nightly chứa PG dump ⇒ gần như chắc chắn chứa token. KHÔNG tuyên bố là đã đo.
- Drive Trash (13.9 GiB)
- Nhánh GitHub vps-daily-*: không fetch (fetch = mang secret về). Suy luận: git object = 0 ở cả 3 repo
  ⇒ token KHÔNG ở trong bất kỳ nhánh nào — đây là suy luận MẠNH vì GitHub chỉ nhận từ chính 2 repo này.
- File nhị phân/nén (grep --binary-files=without-match bỏ qua)

⇒ Dùng CURRENT_AND_HISTORICAL_FOUND, không dùng NOT_FOUND_IN_EXHAUSTIVE_SCOPE.

14. POTENTIALLY-VALID SESSION INVENTORY (PRE)

POTENTIALLY_VALID_SESSIONS = 73   (100% thuộc admin@example.com; 0 session của user khác)
TTL = 7 ngày (REFRESH_TOKEN_TTL); expires ∈ [2026-07-18 16:00Z, 2026-07-25 10:00Z]
n ip user_agent Diễn giải
65 172.18.0.5 Python-urllib/3.11 agent-data → 2 session mỗi 6h = reconcile cron
6 172.18.0.6 node nuxt, cụm một lần 2026-07-17 04:01–05:28 (ngày deploy Balo)
2 38.242.240.89 curl/8.5.0 host, 2026-07-17 03:55–03:58

0 session có user-agent trình duyệtkhông có người nào đang đăng nhập Directus admin UI. Mẫu tạo session khớp tuyệt đối cron 0 */6 CEST (04/10/16/22 UTC, đúng 2/lần) + cụm 10 session lúc 2026-07-17 20 UTC = chính mission C1 (auth smoke + dry-run). Sessions hết hạn bị dọn ở mỗi lần login (DELETE FROM directus_sessions WHERE expires < now()).

15. SESSION / MACHINE DEPENDENCY ASSESSMENT

ADMIN_SESSION_DEPENDENTS = IDENTIFIED  →  NO_CRITICAL_MACHINE_DEPENDENCY_FOUND
Consumer Cơ chế Có phụ thuộc session cũ?
reconcile-{tasks,knowledge} password login mỗi lần chạy ❌ tạo session MỚI mỗi run, không tái dùng
agent-data static token ❌ stateless (đã chứng minh)
nuxt static token ❌ stateless; 6 session là rác một lần
claude-mcp token tac-agent ❌ không liên quan admin
dot-orphan-scanner, dot-misclass-scanner DIRECTUS_ADMIN_TOKEN ❌ stateless
dot-dot-health, dot-collection-health, dot-apr-health, dot-dot-coverage password login ❌ login mới mỗi run (02–05h)
dot-apr-execute (*/5) 🔴 HỎNG — không chạm được Directus ❌ không phải consumer

⇒ Không HOLD_CRITICAL_SESSION_DEPENDENCY_UNMAPPED. Xoá session = không làm gãy service nào. Đã chứng minh sau drill: xoá sạch 75 session, reconcile dry-run vẫn PASS và tự tạo lại 2 session.

16. docker/.env VALUE-LOCATION PROOF (PAYLOAD REDACTED)

Đo bằng giá trị, HMAC khoá ngẫu nhiên (đã shred):

dotenv.DIRECTUS_ADMIN_PASSWORD      len=23  fp=P   ┐
protected.DIRECTUS_PASSWORD         len=23  fp=P   ├ MỘT giá trị duy nhất, ba nơi
directus(container).ADMIN_PASSWORD  len=23  fp=P   ┘
dotenv.DIRECTUS_ADMIN_EMAIL         len=17  fp=E == directus.ADMIN_EMAIL   (= "admin@example.com")

docker/.env DIRECTUS_ADMIN_PASSWORD chính là password đang hiệu lực, và chính là giá trị trong protected file của C1. Không có VALUES_DIFFER.

17. EFFECTIVE COMPOSE / ENV INJECTION

Đọc raw YAML (không docker compose config — tránh render secret; và v5.0.2 không có --no-interpolate):

directus:
  image: directus/directus:11.5
  environment:
    ADMIN_EMAIL:    ${DIRECTUS_ADMIN_EMAIL}
    ADMIN_PASSWORD: ${DIRECTUS_ADMIN_PASSWORD}     # ← CÓ inject
    KEY: ${DIRECTUS_KEY}   SECRET: ${DIRECTUS_SECRET}
    RATE_LIMITER_ENABLED: "false"

Runtime container env (chỉ đọc TÊN): ADMIN_EMAIL, ADMIN_PASSWORD đều có mặt. Runtime variable name = ADMIN_PASSWORD (không phải DIRECTUS_ADMIN_PASSWORD). Không có ADMIN_TOKEN trong container ⇒ static token không do bootstrap seed; được đặt sau qua UI/API.

18. ENTRYPOINT / BOOTSTRAP RE-APPLY ANALYSIS

DIRECTUS_BOOTSTRAP_PASSWORD_BEHAVIOR = BOOTSTRAP_ONLY_NO_REAPPLY
Entrypoint: ["docker-entrypoint.sh"]
Cmd:        /bin/sh -c ": && node cli.js bootstrap && pm2-runtime start ecosystem.config.cjs ;"

bootstrap chạy mỗi lần container khởi động. Nhưng đọc source thật trong image đang chạy (@directus/api/dist/cli/commands/bootstrap/index.js):

if ((await isInstalled()) === false) {
    await installDatabase(database); await runMigrations(database,'latest');
    if (skipAdminInit == null) { await createDefaultAdmin(schema); }   // ← CHỈ ở nhánh này
} else {
    logger.info('Database already initialized, skipping install');
    await runMigrations(database, 'latest');                           // ← KHÔNG đụng user
}
  • createDefaultAdmin gọi usersService.createOne(...)CREATE, không bao giờ UPDATE.
  • isInstalled() = inspector.hasTable('directus_collections') ⇒ DB đã cài ⇒ luôn vào nhánh else.
  • Không cron/helper nào re-apply: grep toàn scripts/ + dot/bin/ + crontab ⇒ 0 đường re-apply.

Restart Directus KHÔNG ghi đè password. C2B không bắt buộc phải sửa docker/.env trong cùng transaction để rotation "dính" — nhưng vẫn PHẢI sửa, vì: (a) nếu DB bị re-init/khôi phục từ đầu, bootstrap sẽ dùng ADMIN_PASSWORD cũ; (b) docker/.env là bản plaintext của giá trị cũ ⇒ để lại là để lại chìa đã lộ. ⇒ Không HOLD_DIRECTUS_ENV_REAPPLY_CONFLICT.

19. CURRENT PASSWORD CONSUMER PATH

/etc/incomex/reconcile/directus-admin.env   (root:root 0600, dir 0700, 44 B, dạng DIRECTUS_PASSWORD='<val>')
  → /opt/incomex/scripts/reconcile-{knowledge,tasks}.sh
      · gate: file tồn tại + readable + mode==600 + owner==root  (thiếu ⇒ exit 3)
      · set -a ; . "$ENVFILE" ; set +a
      · gate: biến rỗng ⇒ exit 3
      · docker cp <script>.py incomex-agent-data:/tmp/…
      · docker exec -e DIRECTUS_PASSWORD incomex-agent-data python3 /tmp/….py   ← CHỈ TÊN BIẾN
  → reconcile-*.py : os.environ.get("DIRECTUS_PASSWORD","") ; rỗng ⇒ exit 3 (fail-closed, không fallback)
Cron: 0 */6 * * *  (Europe/Berlin) ⇒ 00/06/12/18 CEST

Consumer password khác (qua cron-env.shdocker/.env): dot-dot-health 03:00, dot-collection-health 04:00, dot-apr-health 04:30, dot-dot-coverage CN 02:00, dot-apr-execute */5 (hỏng).

20. DISPOSABLE CONSUMER ROUND-TRIP

CONSUMER_ENV_ROUNDTRIP = PASS

Đường đo đúng semantic production, giá trị giả dùng một lần, shred ngay, không gửi tới Directus, không đụng protected file thật: protected test file (0600 root)gate perm/ownerset -a; . file; set +adocker exec -e DIRECTUS_PASSWORD → tiến trình trong container → so fingerprint HMAC.

Case Charset Kết quả
A alphanumeric ROUNDTRIP_PASS
B $ ` " \ ! # & ; | * () {} [] space % ^ @ ~ + = ROUNDTRIP_PASS
C chứa dấu nháy đơn ' FAIL-CLOSED (đúng như thiết kế)

Case C chi tiết: . file báo unexpected EOF while looking for matching ', biến unbound, set -u bắt, wrapper exit 3. ⇒ Không cắt xén âm thầm, không giá trị sai lệch.

🔴 Ràng buộc C2B rút ra: candidate password không được chứa '. Mọi ký tự đặc biệt khác an toàn. (Không ép [A-Za-z0-9] — policy không đòi, và B đã chứng minh không cần.)

21. EXTRACTED PRODUCTION-VALUE LOGIN PROOF (§16 — chống vòng luẩn quẩn)

Chạy sau Owner GO, ngay trước drill, bằng đúng helper và đúng loader sẽ dùng cho drill:

EXTRACTED_CURRENT_PASSWORD_AUTH = PASS
LOGIN_CHECK | login_http=200 | me_http=200 | id=6abdec55-… | email=admin@example.com | logout_http=204 | MATCH=True
sessions 75 → 75   (login tạo 1, logout xoá 1 ⇒ net 0)

⇒ Giá trị trích ra thật sự mở được khoá trước khi dùng nó để set. Không HOLD_EXTRACTED_VALUE_MISMATCH.

22. SECRET MANAGER — PROJECT / RESOURCE / IAM (METADATA ONLY)

project github-chatgpt-ggcloud (812872501910, ACTIVE) · 32 secret · replication user-managed europe-west3
SM_PASSWORD_MAPPING     = UNAMBIGUOUS_NEW_RESOURCE_REQUIRED
SM_STATIC_TOKEN_MAPPING = EXACT_EXISTING_RESOURCE
Kết quả
DIRECTUS_ADMIN_TOKEN TỒN TẠI — 1 version, enabled, createTime 2026-05-22T10:44Z, chưa từng xoay
DIRECTUS_ADMIN_PASSWORD NOT_FOUND
rotation policy không có (cả secret lẫn project)
IAM cấp secret không có binding riêng ⇒ kế thừa project
quyền của Owner roles/owner (+ iam.serviceAccountTokenCreator, workloadIdentityPool*)

Quy ước đặt tên SM phản chiếu chính xác tên biến .env (AGENT_DATA_API_KEY, PG_PASSWORD, QDRANT_LOCAL_API_KEY, DIRECTUS_KEY, DIRECTUS_SECRET…) ⇒ tên mới không mơ hồ = DIRECTUS_ADMIN_PASSWORD. Quyền C2B (create secret / add version / access version / read metadata) đã đủ qua roles/ownerkhông cần tăng IAM. C2A không tạo/không thêm version/không sửa IAM.

23. SM AUTO-APPLY / ROTATION / VERSION POLICY

SM_LATEST_AUTO_APPLY = NO

Không service nào đọc SM (survey §5.1, tái xác nhận: không SM client trong container, không agent sync) ⇒ không có automation lấy latest rồi tự áp ⇒ không conflict C2B. 🔴 Mặt trái: SM chưa từng được đối chiếu với runtime ⇒ chưa chắc restore đúng giá trị đang chạy. C2A cố ý không đọc payload nên không thể khẳng định SM DIRECTUS_ADMIN_TOKEN v1 == token live. ⇒ C2B phải đối chiếu (HMAC read-back) trước khi coi SM là recovery source.

24. OFF-VPS OWNER ACCESS

OFF_VPS_GCP_ACCESS = VERIFIED   (kèm caveat bên dưới)

Bằng chứng đo trực tiếp từ laptop Owner, không phải VPS:

active account = nmhuyen@gmail.com   (user principal, KHÔNG phải service account)
project        = github-chatgpt-ggcloud   →  describe OK, secrets list OK (32), IAM read OK
project role   = roles/owner
VPS identity   = cursor-ci-builder@…iam.gserviceaccount.com   ⇒ ĐƯỜNG TRUY CẬP ĐỘC LẬP với VPS

⚠️ Caveat trung thực: đây là credential đã cache trên laptop. Nó chứng minh quyền truy cập off-VPS đang hoạt động, nhưng không chứng minh Owner đăng nhập lại được nếu mất laptop. Checklist Owner nên làm một lần (không gửi payload vào chat/report):

  1. Mở trình duyệt, đăng nhập Google Cloud bằng tài khoản cá nhân.
  2. Mở đúng project github-chatgpt-ggcloud.
  3. Xem metadata một secret (không mở payload).
  4. Xác nhận không phụ thuộc session/key chỉ nằm trên VPS.

25. ROLLBACK METHOD DESIGN

ROLLBACK_PATH_B = STATIC_TOKEN_METHOD_IDENTIFIED_SECRET_SAFE → PROVEN_BY_STATIC_TOKEN_NOOP_DRILL
auth method    static token của chính bootstrap admin  (Authorization: Bearer, dựng trong process memory)
endpoint       PATCH http://incomex-directus:8055/users/6abdec55-…?fields=id,email,status
payload        {"password": "<value>"}          ← ĐÚNG MỘT FIELD
target         6abdec55-d911-44df-af96-3cf60b9654af
permission     FULL_ADMIN (đã chứng minh §8)
helper         /root/c2a-work/c2a-rollback.py (0700) — sha256 08fa8cf836986c72…
               3 mode: verify (GET) · login-check (login→me→logout) · apply (PATCH)
token nguồn    STDIN duy nhất  →  KHÔNG argv, KHÔNG env host, KHÔNG log
password nguồn protected file → set -a → docker exec -e DIRECTUS_PASSWORD (CHỈ TÊN) → os.environ
API verify     chỉ đọc HTTP status + id/email/status; body bị loại bỏ; header không bao giờ log

🔴 Directus CLI bị loại có căn cứ: node cli.js users passwd --email <value> --password <value>CLI_ARGV_SECRET_UNSAFE = YESkhông dùng. ?fields=id,email,status được thêm để static token không thể xuất hiện trong response body. ❌ Không direct SQL. ❌ Không sửa hash bằng SQL.

26. SECRET-IN-ARGV / HEADER / LOG SAFETY PROOF

✅ Không echo/print/log bất kỳ giá trị secret nào
✅ Token: chỉ qua stdin (`sed … | docker exec -i …`) — chưa từng vào argv, chưa từng vào env của shell host
✅ Password: chỉ qua `docker exec -e NAME` (name-only) — value không vào argv
✅ grep secret: luôn `-f <tmpfs file>` — KHÔNG literal trong argv (sửa đúng lỗi survey §18 tự khai)
✅ Không `curl -H "Authorization: Bearer <token>"`
✅ Không ghi raw docker inspect Env · không ghi raw request/response · không bật HTTP verbose
✅ Không đọc payload Secret Manager (0 lần `secrets versions access`)
✅ HMAC key ngẫu nhiên 32 B trong tmpfs, đã `shred` ⇒ fingerprint MỘT CHIỀU, vô dụng ngoài phiên này
✅ trap EXIT shred mọi pattern file · /dev/shm CLEAN sau mission
Kiểm chứng sau mission:
   ps -eo args | grep -F -f <pattern>          → CLEAN (0 hit)
   docker logs directus/agent-data --tail 400  → 0 hit secret
   /dev/shm                                    → CLEAN
   in-container helper /tmp/c2a-*              → đã xoá

27. READ-ONLY READINESS GATES (tất cả PASS trước khi xin GO)

ADMIN_TOPOLOGY=VERIFIED_SINGLE_BOOTSTRAP_ADMIN      NMHUYEN_ACCOUNT=UNCHANGED_METADATA_ONLY
TFA_STATUS=KNOWN_AND_MANAGEABLE (DISABLED)          PASSWORD_POLICY=KNOWN (none enforced)
LOCKOUT_POLICY=KNOWN (25 consecutive → suspend)     STATIC_TOKEN_AUTH=PASS
STATIC_TOKEN_EFFECTIVE_ACCESS=FULL_ADMIN            STATIC_TOKEN_EXPOSURE=CLASSIFIED
ADMIN_SESSION_DEPENDENTS=NO_CRITICAL_DEPENDENCY     BOOTSTRAP_REAPPLY_BEHAVIOR=KNOWN
CONSUMER_ENV_ROUNDTRIP=PASS                         SM_MAPPING=NON_AMBIGUOUS
SM_AUTO_APPLY=NONE                                  ROLLBACK_PATH_B=SECRET_SAFE_METHOD_IDENTIFIED
GIT_SAFETY_GATE=PASS                                SAFE_WINDOW=AVAILABLE
CONTAINER_PRE_SNAPSHOT=COMPLETE                     SECRET_HYGIENE=PASS
BALO_GUARD_PRE=PASS (37/37)

28. OWNER GO REQUEST AND RESPONSE

In ra: "C2A_ROLLBACK_DRILL_READY — OWNER GO REQUIRED" + tóm tắt đầy đủ §23 của mission
Owner trả lời: GO_C2A_ROLLBACK_DRILL       (chuỗi chính xác, không suy diễn từ "ok" mơ hồ)

Side effect đã công bố trước khi xin GO: PATCH password sẽ xoá toàn bộ ~75 session.

29. SAFE WINDOW + NO-OP ROLLBACK DRILL

SAFE_WINDOW = 2026-07-18 13:51 → 18:00 CEST   (cửa sổ giữa hai lần reconcile)
  đo live: reconcile 0 */6 CEST → lần kế 18:00 · git-push-gh 0 6,18 CEST → 18:00
           code-backup 15:00 CEST (ICT 20:00) · nightly 20:37 CEST
  dot-hc-executor / run-sr-mow / hc-executor-verify: 0 tham chiếu Directus auth
  Lúc bắt đầu drill: 0 job reconcile/backup/push đang chạy
DRILL START = 14:10:47 CEST     PATCH ghi nhận trong log Directus lúc 12:10:49Z = 14:10:49 CEST

Revalidate ngay trước drill (§24): helper sha256 khớp ở cả 3 vị trí · protected file fingerprint sha256 == PRE · admin active (không suspended) · static token PASS · password login PASS.

APPLY | http=200 | id=6abdec55-… | email=admin@example.com | status=active     APPLY_RC=0
Log Directus:  PATCH /users/6abdec55-…?fields=id,email,status  200  222ms   ← ĐÚNG 1 lần, ĐÚNG 1 user

30. TIMEOUT / AMBIGUOUS-RESULT HANDLING

Không phát sinh. PATCH trả 200 trong 222 ms, APPLY_RC=0, không timeout, không kết quả mơ hồ. Cây quyết định §26 (kiểm password → kiểm token → kiểm metadata → không retry mù) đã sẵn sàng nhưng không cần dùng. DRILL_RESULT_INCONCLUSIVE_* không áp dụng.

31. PRE / POST ADMIN METADATA

                          PRE                                          POST
id                        6abdec55-d911-44df-af96-3cf60b9654af   ==     6abdec55-d911-44df-af96-3cf60b9654af
email                     admin@example.com                      ==     admin@example.com
status                    active                                 ==     active
provider                  default                                ==     default
role                      a40a1070-0b62-4a7e-b2c0-bd4bce9d41ac   ==     a40a1070-0b62-4a7e-b2c0-bd4bce9d41ac
tfa_secret present        false                                  ==     false
token present             true                                   ==     true
password hash length      97                                     ==     97
ADMIN_ID_PRE=POST                        ✅
ADMIN_EMAIL_PRE=POST                     ✅
ADMIN_ROLE_POLICIES_PRE=POST             ✅  (policy topology đo lại: vẫn 1 admin policy / 1 role / 1 user)
ADMIN_STATUS_PRE=POST                    ✅
ADMIN_TFA_PRE=POST                       ✅
ADMIN_STATIC_TOKEN_FINGERPRINT_PRE=POST  ✅  9e2f904336904179 == 9e2f904336904179
CURRENT_PASSWORD_PLAINTEXT_CHANGED       = NO  (pw.dotenv & pw.protected fp c7c676f81ed8b19d PRE==POST,
                                                protected_file_sha256 c1e2824b… PRE==POST)

🔑 Bằng chứng WRITE thật sự đã xảy ra (không phải no-op giả):

password ARGON2 HASH  PRE 3c3751541e585bdc  →  POST 931cfcf4885ed88a   *** ĐỔI ***

Cùng plaintext, salt argon2 mới ⇒ Directus thật sự đã ghi lại password. Đây chính là điều biến drill thành bằng chứng có giá trị: đường ghi đã được thực thi đầu-cuối, mà plaintext không đổi.

32. CURRENT PASSWORD LOGIN PROOF (POST)

CURRENT_PASSWORD_AUTH = PASS
LOGIN_CHECK | login_http=200 | me_http=200 | id=6abdec55-… | email=admin@example.com | logout_http=204 | MATCH=True

Dùng chính giá trị plaintext cũ, trích bằng chính helper/loader — đăng nhập được sau drill.

33. STATIC TOKEN PRE / POST

STATIC_TOKEN_AUTH_POST = PASS
VERIFY | http=200 | id=6abdec55-… | email=admin@example.com | status=active | role=Administrator
fingerprint DB token   PRE 9e2f904336904179  ==  POST 9e2f904336904179
fingerprint .env token PRE 9e2f904336904179  ==  POST 9e2f904336904179
STATIC_TOKEN_PASSWORD_RESET_BEHAVIOR = STATIC_TOKEN_SURVIVES_PASSWORD_RESET_PROVEN

Phao rollback vẫn nguyên vẹn sau khi đổi password. Đây là điều kiện tiên quyết để C2B dám rotate password.

34. RECONCILE DRY-RUN PROOFS (POST)

RECONCILE_TASKS_DRY_RUN     = PASS   rc=0  "Tasks: 10 synced, 0 missing · Comments: 75 synced, 0 missing · All in sync!"
RECONCILE_KNOWLEDGE_DRY_RUN = PASS   rc=0  "All in sync!" · "Created: 0 (would create)"
Chạy trong env cron-equivalent: env -i HOME=/root LOGNAME=root PATH=/usr/bin:/bin SHELL=/bin/sh
FATAL=0 · Traceback=0 · strict HTTP-401/403 & auth-failure markers = 0 · WRITE = 0

⚠️ Ghi nhận minh bạch: lần grep đầu của tôi báo "71 error markers" ở reconcile-knowledge. Đó là dương tính giả do chính regex của tôi401/403 khớp id tài liệu Directus (id=401, id=2403) và tên file fix-permissions-403-report. Đếm chặt theo word-boundary/ngữ cảnh HTTP = 0. Tôi kiểm lại thay vì báo cáo một lỗi không tồn tại, và cũng không lặng lẽ bỏ qua nó.

35. SESSION BEHAVIOR RESULT

PASSWORD_RESET_SESSION_BEHAVIOR = ALL_OBSERVED_SESSIONS_INVALIDATED
sessions: 75 (trước PATCH)  →  0 (sau PATCH)  →  2 (sau reconcile dry-run, cả 2 từ 172.18.0.5 Python-urllib)

Dự đoán trước khi chạy, từ source (services/users.js):

else if (data['password'] !== undefined || data['email'] !== undefined) {
    await this.clearUserSessions(keys, this.accountability?.session);
}
// excludeSession undefined  ⇒  DELETE FROM directus_sessions WHERE user IN (keys)

Vì drill xác thực bằng static token (không có session), accountability.session = undefinedxoá sạch, không chừa. Đúng như dự đoán. ⇒ Bài học vận hành cho C2B: đổi password đổi email đều xoá session. Nếu C2B thực hiện từ một session (password login) thì session đang dùng sẽ được chừa lại — khác hành vi với static token. Và: payload C2B chỉ được chứa password; kèm email sẽ nhân đôi tác động ngoài ý muốn.

36. BALO GUARD

BALO_ONE_CLICK_GUARD = PASS | checks=37     (PRE 37/37 == POST 37/37)
"PASS fingerprint unchanged by read-only probes"

37. KB / MCP READ HEALTH

KB_MCP_READ_HEALTH = PASS
list_documents ✅ · batch_read (full) ✅ · search_knowledge semantic ✅ · agent-data /health = 200

38. CONTAINER / SERVICE ZERO-TOUCH

CONTAINER_IDS_PRE=POST     ✅ 11/11 SAME_ID (không recreate)
STARTED_AT_PRE=POST        ✅ diff containers.DRILL_PRE vs POST/FINAL = IDENTICAL (0 dòng khác)
OTHER_SERVICE_MUTATION     = 0
SERVICE_RESTART            = NO      DIRECT_DB_WRITE = NO

Thao tác duy nhất chạm container: docker cp + docker exec vào incomex-agent-data (đúng hành vi wrapper thường ngày) và docker exec postgres psql read-only SELECT. Không restart/recreate gì.

⚠️ Nhiễu nền phải khai báo (KHÔNG do mission gây ra): incomex-nuxt tự restart trong lúc tôi làm read-only — RestartCount 7 → 9, StartedAt 10:10:23Z → 11:51:59Z, cùng container ID.

ExitCode=0 · OOMKilled=false · RestartPolicy=unless-stopped
⇒ tiến trình THOÁT SẠCH rồi bị Docker khởi động lại — không phải crash, không phải OOM, không liên quan auth
   (các 403 trong log Directus là thiếu quyền Public policy trên /items/pages…, không dính admin credential)

Vì vậy phép chứng minh zero-touch dùng container ID cho cả 11 (bắt recreate) cộng StartedAt/RestartCount, và bọc drill bằng ảnh chụp sát hai đầu. Trong đúng cửa sổ drill, cả 11 container identical, kể cả nuxt. 🔴 Đây là vấn đề production độc lập đáng điều tra riêng (nuxt tự thoát ~1–2 giờ/lần).

39. C2B LOCKED SCOPE

C2B_SCOPE = ROTATE_PASSWORD_AND_STATIC_TOKEN_AS_ONE_ADMIN_CREDENTIAL_SET

Bắt buộc theo §12/§29 vì ADMIN_STATIC_TOKEN_EXPOSURE = CURRENT_AND_HISTORICAL_FOUND (37 file hiện hành + mọi PG dump + Drive nightly). Không được mặc định password-only.

40. C2B SEQUENCE DESIGN — THIẾT KẾ, KHÔNG THỰC HIỆN

 1. Giữ static token CŨ làm phao rollback tạm thời (đã chứng minh sống sót qua password reset).
 2. Sinh candidate password thoả policy (không policy) — 🔴 KHÔNG chứa dấu nháy đơn `'` (§20 case C).
 3. Round-trip candidate qua exact consumer path → PASS trước khi đụng Directus.
 4. Tạo SM secret MỚI `DIRECTUS_ADMIN_PASSWORD` + add version (Owner có roles/owner, đủ quyền).
 5. Read-back HMAC PASS (đối chiếu SM ↔ giá trị định áp — vá luôn khoảng trống "SM chưa từng đối chiếu").
 6. PATCH /users/<id> {"password": …} bằng static token cũ  — CHỈ field password.
 7. Materialize protected cache: ghi lại /etc/incomex/reconcile/directus-admin.env (atomic, 0600 root).
 8. Cập nhật `docker/.env` DIRECTUS_ADMIN_PASSWORD (bootstrap KHÔNG re-apply, nhưng đây là bản plaintext
    của chìa cũ + là nguồn nếu DB bị re-init) — trong cùng transaction credential.
 9. Login PASS bằng password mới.
10. reconcile-{tasks,knowledge} --dry-run PASS  +  dot-*-health consumers PASS.
11. CHỈ KHI đường password chắc chắn → rotate static token (PATCH token).
12. Ghi token mới vào SM `DIRECTUS_ADMIN_TOKEN` version mới (nếu Owner duyệt).
13. New static token /users/me PASS.
14. Old static token bị từ chối (đúng 1 lần đo).
15. Old password bị từ chối — ĐÚNG MỘT LẦN, ở cuối (nhớ: 25 fail liên tiếp ⇒ suspend; 1 lần là an toàn).
16. Cleanup phao tạm + shred key.
Sau C2B: C3 rewrite git history (2 object password) + xử lý nhánh GitHub; C4 xoá .env.bak* + evidence JSON
         + token.txt + sửa /root/.bashrc; quyết định retention Drive (dump cũ vẫn mang token cũ).

🔴 Cảnh báo cho C2B: rotate password không vô hiệu hoá static token, và rotate token không vô hiệu hoá password trong git history. Chỉ làm cả hai + sanitize mới đóng được exposure.

41. FUTURE PERSONNEL / IDENTITY SSOT BACKLOG

Ghi nhận, không triển khai: bảng nhân sự làm SSOT danh tính → ánh xạ sang Directus user/role/policy → bootstrap admin trở thành break-glass hoặc bị vô hiệu hoá. Dữ liệu hữu ích C2A để lại cho việc đó: nmhuyen@gmail.com đã active + có password + có token nhưng role NULL ⇒ chỉ cần cấp policy là dùng được, không cần tạo user mới. 7 user agent.* đang suspended nhưng vẫn giữ static token.

42. GIT COMMITS AND BUNDLES

COMMITS = 0   (báo cáo đi thẳng KB; không stage, không commit, không push)
BUNDLES : opt-incomex-PRE 962a2cfe… · agent-data-repo-PRE 8d860734…  — cả 2 verify PASS
          root-only /root/c2a-checkpoint-…/bundle, ngoài Drive/GitHub/KB
HEAD PRE == HEAD POST : /opt/incomex 2b086472 · agent-data-repo 7f13207f
dirty /opt/incomex PRE == POST : 104 (15 M + 89 ??)  — unrelated work giữ nguyên

43. EVIDENCE PATHS

CHECKPOINT /root/c2a-checkpoint-20260718T110800Z          (root:root 0700)
  INTERIM-STATE.md                                        (0600, không chứa giá trị secret)
  bundle/{opt-incomex,agent-data-repo}-PRE.bundle + bundles.sha256   (verify PASS)
  evidence/containers.{PRE,DRILL_PRE,POST,FINAL}
  evidence/fingerprints.{PRE,POST}
  work/c2a-rollback.py                                    (sha256 08fa8cf836986c72…)
WORKDIR    /root/c2a-work/{c2a-fp.py, c2a-rollback.py, scan-*.txt}   (0700)
KEY        work/mission-hmac.key → ĐÃ SHRED sau POST ⇒ fingerprint một chiều, vô dụng ngoài phiên
LỆNH ĐO    docker exec postgres psql -tA (SELECT read-only) · docker inspect --format (names/StartedAt)
           · docker exec incomex-directus cat/sed/grep <source .js trong image>  (đọc source thật)
           · grep -rlF -f <tmpfs pattern>  · git rev-list --objects --all | git cat-file --batch | grep -cF -f
           · git bundle create/verify · gcloud secrets list/describe/versions list/get-iam-policy (metadata only)
           · crontab -l · systemctl · dot-balo-one-click-guard

44. PAYLOAD = REDACTED PROOF

Báo cáo này chứa: tên biến, độ dài byte, fingerprint HMAC-SHA256 rút gọn 16 hex (khoá ngẫu nhiên 32 B đã shred), và REDACTED. Không password, không token, không API key, không hash xác thực, không connection string, không TLS key. Fingerprint không đảo ngược đượcvô dụng ngoài phiên này — chúng chỉ chứng minh quan hệ bằng/khác giữa các giá trị tại thời điểm đo.


KẾT THÚC BẮT BUỘC — SCOPE PROOF

DIRECTUS_NEW_PASSWORD_APPLIED   = NO      (chỉ set lại CHÍNH plaintext hiện hành)
DIRECTUS_PASSWORD_PLAINTEXT_CHANGED = NO
DIRECTUS_STATIC_TOKEN_ROTATED   = NO      DIRECTUS_STATIC_TOKEN_REVOKED = NO
DIRECTUS_SESSIONS_REVOKED       = NO      (không chủ động thu hồi; 75 session bị xoá là TÁC DỤNG PHỤ
                                           tất yếu của password PATCH, đã công bố trước khi xin GO)
DIRECTUS_EMAIL_CHANGE           = NO      DIRECTUS_ROLE_POLICY_CHANGE   = NO
DIRECTUS_USER_CREATION          = NO      NMHUYEN_ACCOUNT_CHANGE        = NO
TFA_CHANGE                      = NO      PERSONNEL_TABLE_CHANGE        = NO
SECRET_MANAGER_MUTATION         = NO      RUNTIME_ENV_CHANGE            = NO
DOCKER_ENV_CHANGE               = NO      CONTAINER_MUTATION            = NO
SERVICE_RESTART                 = NO      DIRECT_DB_WRITE               = NO
PRODUCTION_RECONCILE_WRITE      = NO      PRODUCTION_DRIVE_BACKUP_RUN   = NO
GITHUB_PUSH                     = NO      GIT_HISTORY_REWRITE           = NO
HISTORICAL_BACKUP_DELETE        = NO      DOCKER_CLEANUP                = NO
GIT_COMMIT                      = NO

Mutation đã thực hiện, ĐÚNG MỘT:

PATCH /users/6abdec55-d911-44df-af96-3cf60b9654af?fields=id,email,status  {"password": <giá trị hiện hành>}
→ HTTP 200, 222 ms — password hash được ghi lại (salt mới), plaintext KHÔNG đổi
→ tác dụng phụ tất yếu: 75 directus_sessions bị xoá (đã phục hồi bình thường: reconcile tự tạo lại)

FACTS NEXT AGENT MAY RELY ON

✅ admin@example.com = 6abdec55-d911-44df-af96-3cf60b9654af, full-admin DUY NHẤT (1/13)
✅ 1 policy admin_access=t (8a613123) → gắn 1 role (a40a1070) → 1 user. Roles KHÔNG có parent.
✅ TFA tắt toàn bộ 13 user · auth_password_policy=NULL (KHÔNG policy) · auth_login_attempts=25
✅ Lockout = 25 fail LIÊN TIẾP ⇒ status='suspended'; login thành công RESET bộ đếm; duration:0; store=memory
✅ Directus 11.5.1 lưu static token PLAINTEXT trong directus_users.token (đo bằng giá trị)
✅ .env DIRECTUS_ADMIN_TOKEN == directus_users.token(admin) == agent-data == nuxt
✅ claude-mcp DIRECTUS_TOKEN = token của tac-agent, KHÔNG phải admin ⇒ rotate admin token không gãy claude-mcp
✅ Static token auth STATELESS: không tạo session, không cập nhật last_access
✅ bootstrap chạy mỗi lần start NHƯNG createDefaultAdmin chỉ khi isInstalled()==false ⇒ KHÔNG re-apply
✅ PATCH password ⇒ clearUserSessions ⇒ xoá TOÀN BỘ session của user (trừ session của chính caller nếu có)
✅ Static token SỐNG SÓT qua password reset (đã chứng minh bằng drill)
✅ Protected file dùng dạng DIRECTUS_PASSWORD='<val>' ⇒ giá trị chứa `'` sẽ FAIL-CLOSED (exit 3)
✅ Mọi ký tự đặc biệt khác round-trip PASS qua set -a → docker exec -e
✅ SM: DIRECTUS_ADMIN_TOKEN CÓ (v1, chưa xoay); DIRECTUS_ADMIN_PASSWORD KHÔNG CÓ; Owner = roles/owner
✅ Không service nào đọc SM ⇒ SM_LATEST_AUTO_APPLY=NO
✅ nmhuyen@gmail.com: active, password + token CÓ, role NULL, chưa từng login
✅ dot-apr-execute (*/5) HỎNG: không kết nối được localhost:8055 ⇒ KHÔNG phải consumer Directus
✅ Reconcile tạo session MỚI mỗi run ⇒ xoá session cũ vô hại
✅ backup-to-gdrive.sh L43 pg_dump directus ⇒ static token plaintext rời VPS mỗi đêm, không mã hoá
✅ /root/.bashrc:107 export DIRECTUS_ADMIN_TOKEN (chỉ ảnh hưởng shell TƯƠNG TÁC; non-interactive bị L6 chặn)

ASSUMPTIONS NEXT AGENT MUST NOT MAKE

❌ ĐỪNG coi C2A đã đóng exposure — KHÔNG đổi password, KHÔNG rotate token
❌ ĐỪNG gộp password và token thành một đường rò: password ở GIT HISTORY, token ở PG DUMP. Khác nhau hoàn toàn.
❌ ĐỪNG nghĩ rewrite git history (C3) sẽ dọn được static token — token có 0 object trong git
❌ ĐỪNG nghĩ rotate token dọn được password — password nằm trong git object + GitHub
❌ ĐỪNG dùng `directus users passwd` CLI — password vào argv (CLI_ARGV_SECRET_UNSAFE=YES)
❌ ĐỪNG gửi kèm `email` trong payload PATCH password — cũng kích hoạt clearUserSessions
❌ ĐỪNG sinh candidate password chứa dấu nháy đơn `'` — protected-file format sẽ fail-closed
❌ ĐỪNG thử password sai để "kiểm tra" — 25 lần liên tiếp ⇒ tài khoản admin bị SUSPENDED
❌ ĐỪNG tin SM restore được production — chưa từng đối chiếu payload ↔ runtime (C2A cố ý không đọc payload)
❌ ĐỪNG coi 73 session là 73 consumer đang hoạt động — chúng là rác của script login-rồi-không-logout
❌ ĐỪNG coi nuxt RestartCount tăng là do mission — nuxt tự thoát ExitCode=0 ~1–2h/lần, có từ trước
❌ ĐỪNG grep lỗi bằng regex lỏng ('401'/'403' khớp ID tài liệu Directus) — dùng word-boundary/ngữ cảnh HTTP
❌ ĐỪNG chạy code-backup-to-gdrive.sh thủ công (retention 56==REMOTE_KEEP ⇒ xoá artifact cũ)

DỪNG sau báo cáo. Không tự soạn hoặc chạy C2B.

Back to Knowledge Hub knowledge/current-state/reports/claude-c2a-directus-bootstrap-admin-rotation-readiness-2026-07-18.md