KB-592B

Claude — C0: Chặn Git history chứa active secret sao chép lên Google Drive (2026-07-17)

19 min read Revision 1
current-statec0backupgdrivegit-historysecret-containmentpropagation-stoppedworktree-secret-remainsrclonevps-ssotrollback-readyzero-mutation2026-07-17PASS_CURRENT_GIT_HISTORY_PROPAGATION_STOPPED

C0 — Chặn Git history chứa active secret tiếp tục sao chép lên Google Drive (2026-07-17)

Tác giả: Claude (Opus 4.8). Host: vmi3080463 / 38.242.240.89 (Contabo). Nguồn điều hành: survey rev8 (58.569 ký tự) + Wave A1 rev1 (41.831 ký tự) — đọc toàn văn cả hai. Mọi giá trị secret = REDACTED. Không password/token/key nào trong báo cáo này.


1. VERDICT

PASS_CURRENT_GIT_HISTORY_PROPAGATION_STOPPED
CODE_WORKTREE_OFFSITE_BACKUP     = PASS
ACTIVE_GIT_HISTORY_PROPAGATION   = STOPPED
CLEAN_GIT_HISTORY_OFFSITE_BACKUP = TEMPORARILY_UNAVAILABLE_UNTIL_ROTATION_AND_HISTORY_SANITIZATION
HISTORICAL_DRIVE_COPIES          = REMAIN_UNCHANGED

Không tuyên bố historical exposure đã đóng.

🔴 PHÁT HIỆN QUAN TRỌNG NHẤT — C0 KHÔNG chặn được secret rời VPS

Đo thật sau khi vá (không suy luận): artifact mới vẫn mang chính credential đang hoạt động.

Artifact MỚI (.git đã loại) — file chứa LIVE DIRECTUS_ADMIN_PASSWORD:
  web-test          0
  agent-data-test   2   ← scripts/reconcile-knowledge.py
                          scripts/reconcile-tasks.py

Lý do: giá trị nằm đồng thời ở hai chỗ — trong .git objects (2 blob) trong file worktree đang được TRACKED. Loại .git chỉ cắt vector thứ nhất. Hai file worktree là source thật, vẫn cần cho restore, nên §8.3 cấm loại chúng ở C0.

ACTIVE_CREDENTIAL_PROPAGATION_VIA_WORKTREE = CONTINUES (2 file, 4×/ngày, chưa được chặn)

Đính chính khung của mission: tiêu đề C0 nói "chặn Git history" — điều đó đạt. Nhưng nếu ai đó đọc verdict thành "secret đã hết rời VPS" thì SAI. Chặn thật cần Wave C2 (rotate) + C3 (rewrite history) gỡ hardcode khỏi 2 file worktree. Việc này cần Owner quyết, vượt phạm vi C0 (§5 cấm rotate, §8.3 cấm loại source).


2. BACKUP JOB IDENTITY

script     /opt/incomex/scripts/code-backup-to-gdrive.sh   (755 root:root)
PRE  sha256 20762cfb3912f686b0b3bd3b56b66b23f705a5cc98731ef90752e15e803d9e5f
POST sha256 b47c5e36995582939ef55e261543747f3e4e8022893b20b81ac869e269d3ee27
cơ chế     rsync -a → stage → tar -czf → rclone copyto   (KHÔNG mã hoá, KHÔNG crypt)
lock       flock /opt/incomex/backups/code/.code-backup.lock

Không tin tên job — đã xác minh binary thật, cron thật, source thật.

3. SCHEDULE & DESTINATION (không đổi)

cron (root crontab L57)   0 8,12,15,20 * * *   = 4×/ngày  (ICT qua CRON_TZ)
remote                    gdrive-backup:  (rclone, type=drive, scope=drive)
folder web-test           1K1atlFO-bkO7KoukEKPDFbZq7wvQd1Mb
folder agent-data-test    1__qHH5ndV0FqC73nJu5_kUN5sd46M-AN
retention                 REMOTE_KEEP=56  ·  LOCAL_KEEP=1
crontab sha256 PRE == POST  547e8151f95c0cc86ed48abd3401e8af1b04b1ce0f111a14bd2d9b13bc07b039

destination / retention / lịch: KHÔNG đổi (đúng §8.5).

4. PRE SOURCE / INCLUDE / EXCLUDE

WEB_REPO_PATH    /opt/incomex/docker/nuxt-repo         → tar "web-test"
AGENT_REPO_PATH  /opt/incomex/docker/agent-data-repo   → tar "agent-data-test"

rsync -a --exclude=node_modules .nuxt .output __pycache__ .pytest_cache
         .mypy_cache .cache coverage dist .venv          ← 10 exclude, KHÔNG có .git

Chỉ loại build/cache. Toàn bộ hidden files, .git, worktree pointer đều được sao chép. Không follow symlink ngoài cây (rsync -a giữ symlink as-is).

5. GIT ROOTS BỊ ẢNH HƯỞNG (đo, không đoán)

Path Kiểu HEAD Remote
docker/nuxt-repo/.git dir (18M, 1998 file) 2ea41583… main github.com/Huyen1974/web-test
docker/nuxt-repo/web-rp-current/.git 🔴 FILE — worktree pointer d04d8e5 feat/rp-current-supervision
docker/agent-data-repo/.git dir (22M, 381 file) 0ffd6778… main github.com/Huyen1974/agent-data-test
git worktree list (nuxt-repo):
  /opt/incomex/docker/nuxt-repo                 2ea4158 [main]
  /opt/incomex/docker/nuxt-repo/web-rp-current  d04d8e5 [feat/rp-current-supervision]
  → .git file trỏ: gitdir: /opt/incomex/docker/nuxt-repo/.git/worktrees/web-rp-current

Nested repo: không có. Local bundle trong source set: không có (*.bundle/*.pack ngoài .git = 0).

Phân loại nguồn:

Nguồn Lớp
nuxt-repo/.git, agent-data-repo/.git, worktree pointer GIT_HISTORY_WITH_ACTIVE_SECRET
worktree source (trừ 2 file dưới) SAFE_CODE_CONTENT
agent-data-repo/scripts/reconcile-{knowledge,tasks}.py 🔴 RUNTIME_SECRET_FILE (worktree, vẫn trong backup)
.env.example ×4, .env.working SAFE_CODE_CONTENT.env.working chứa Qdrant key (A1 §14 đã đo: ≠ key live)
docker/.env, /etc/incomex/agent-data.env KHÔNG thuộc source set này (đã kiểm)

6. ACTIVE-SECRET HISTORY PROOF (payload REDACTED)

Phương pháp: trích giá trị live vào file tmpfs 0600, dùng grep -Ff <file>. Giá trị KHÔNG bao giờ vào argv — đúng cách mà survey §18 tự khai là đã làm sai. Pattern file đã shred sau khi đo.

pattern bytes: 24   (REDACTED, không in)

HEAD:scripts/reconcile-knowledge.py      match_lines=1
HEAD:scripts/reconcile-tasks.py          match_lines=1
4acebbc:scripts/reconcile-knowledge.py   match_lines=1
4acebbc:scripts/reconcile-tasks.py       match_lines=1
blob trong packed history mang giá trị:  2

.git thật sự mang credential đang hiệu lực ⇒ mỗi lần chạy = 1 bản off-site mới của history chưa sanitize. C0 có căn cứ.

⚠️ Ghi nhận minh bạch: probe đầu của tôi dùng regex theo tên biến (DIRECTUS_ADMIN_PASSWORD *= *["']) → 0 hit, dễ kết luận nhầm "không có secret". Hình dạng gán trong file khác regex đó. Chỉ phép đo theo GIÁ TRỊ mới đúng. Bài học: đo secret phải theo giá trị, không theo tên biến.

7. GIT SAFETY GATE = PASS

repo    /opt/incomex     branch feat/s177-sprint1-round-a
HEAD(PRE)  cdf25ecb2f1406d0515ab43f359520270236eb60   (== HEAD POST của Wave A1 ✅)
remote     KHÔNG CÓ  ⇒ GIT_PUSH bất khả thi về mặt cấu trúc
dirty PRE  15 M + 88 ??  (== A1 ghi nhận ✅)
Path Phân loại Xử lý
scripts/code-backup-to-gdrive.sh MISSION_CHANGE — TRACKED_CLEAN, live == HEAD (diff 0 dòng) không cần baseline commit
15 M + 88 ?? UNRELATED_WORK không stage — POST vẫn đúng 15 M + 88 ??
agent-data-repo HEAD blobs SECRET_IN_HEAD ghi nhận, không sửa ở C0
UNKNOWN 0

Chỉ git add <exact-path>. Không -A, không ., không pull/reset/checkout/restore/clean.

8. CHECKPOINT / BUNDLE / BACKUP

CK = /root/c0-checkpoint-20260717T130000Z    (root:root 0700)

bundle/opt-incomex-PRE-20260717T130000Z.bundle
  a7e0193e012ff86ec5e7778bc2c6df30381077b3dee0858c9454f7d80ed32ef3
bundle/opt-incomex-POST-20260717T130000Z.bundle
  22d550b1f3d1f248cb19a26b2f4290676ff102fed2c3b22a15273986ebc8aac3
  → cả 2: "The bundle records a complete history."  PASS

backup/code-backup-to-gdrive.sh.PRE   20762cfb…  (byte-exact)
backup/root-crontab.PRE               547e8151…
rollback/ROLLBACK.sh                  (0700, 0 external dep: không KB/MCP/GitHub/network)

§6.7 — checkpoint có bị chính job đang sửa upload không? KHÔNG:

code-backup  → chỉ nuxt-repo + agent-data-repo          ⇒ /root không thuộc
nightly      → /opt/incomex/docker (maxdepth 2), nginx *.conf, /etc/letsencrypt,
                /opt/incomex/scripts/*                   ⇒ /root không thuộc
/root/c0-checkpoint-*  không nằm trong git root nào

✅ Tiện thể xác nhận containment của Wave A1 vẫn sống: backup-to-gdrive.sh L79 không còn -name ".env*".

9. MUTATION NHỎ NHẤT — đúng 1 dòng

@@ -189,6 +189,7 @@ backup_repo() (
   stage="rsync-stage"
   rsync -a \
+    --exclude='.git' \
     --exclude='node_modules' \
  • --exclude='.git' basename-match ở mọi độ sâu ⇒ phủ cả 3 git root, gồm worktree pointer dạng FILE (§8.2 yêu cầu) — không cần pattern thứ hai.
  • Không loại source/worktree cần thiết. Không tắt job. Không đổi destination/retention/lịch.
  • bash -n OK · perms 755 root:root giữ nguyên · diff --check CLEAN.

10. POST SOURCE / INCLUDE / EXCLUDE

rsync -a --exclude=.git node_modules .nuxt .output __pycache__ .pytest_cache
         .mypy_cache .cache coverage dist .venv        ← 11 exclude (thêm đúng 1)

11. VERIFICATION — vì sao KHÔNG chạy manual backup

Script không có dry-run/list-only native. Và:

remote_count web-test        = 56
remote_count agent-data-test = 56      REMOTE_KEEP = 56

🔴 Chạy 1 lần manual ⇒ 57 > 56 ⇒ cleanup_remote_retention XOÁ 1 artifact Drive cũ mỗi repo ⇒ vi phạm §5 "không xóa backup Drive cũ". Vì vậy cố ý KHÔNG chạy manual backup.

Thay bằng phép đo mạnh hơn: parse exclude list từ chính file script LIVE đã vá (không hardcode ⇒ không lệch giữa thứ được test và thứ sẽ chạy), rồi chạy đúng rsync + tar đó vào thư mục tạm, không đụng Drive, không retention, không xoá.

exclude parsed từ script live: 11 giá trị, .git đứng đầu ✅

                    PRE members   POST members   .git   worktree-ptr   bundle/pack
web-test               7807          5529          0         0             0
agent-data-test        1149           675          0         0             0

size  agent-data-test  40,996,558 B → 20,035,532 B
      web-test         24,723,112 B → 13,937,297 B

Số học khớp tuyệt đối ⇒ chứng minh chỉ .git bị loại, không mất gì khác:

web-test        7807 − 2277 (.git, đã gồm worktree ptr) − 1 (BACKUP-MANIFEST.txt) = 5529 ✅
agent-data-test 1149 −  473 (.git)                      − 1 (BACKUP-MANIFEST.txt) =  675 ✅

(−1 manifest là hiện vật của harness đo: script thật ghi BACKUP-MANIFEST.txt vào stage trước rsync; harness của tôi không ghi. Artifact thật sẽ có manifest.)

12. ARTIFACT CONTENT PROOF

Yêu cầu §9 Kết quả
không chứa .git ✅ 0 member
không chứa .git worktree pointer ✅ 0
không chứa unsanitized Git bundle ✅ 0 (*.bundle/*.pack = 0)
không chứa runtime env docker/.env/etc/incomex/agent-data.env không thuộc source set
không chứa plaintext secret-bearing scripts 🔴 KHÔNG ĐẠT — 2 file worktree reconcile-*.py vẫn mang credential live (§1)
vẫn chứa source/worktree cần phục hồi ✅ 5104 + 590 file source giữ nguyên
có manifest/listing đủ kiểm tra BACKUP-MANIFEST.txt (branch + head_before ⇒ vẫn đối chiếu được history)
backup job vẫn thành công bash -n OK; đường code không đổi ngoài 1 exclude; lần chạy thật kế: 20:00 ICT = 15:00 CEST hôm nay

Không đọc/in secret payload để kiểm (chỉ đếm file khớp).

13. CODE/WORKTREE OFFSITE BACKUP = PASS

Drive   : vẫn nhận code/worktree 4×/ngày (5529 + 675 member, source đầy đủ)
GitHub  : cả 2 repo, 2×/ngày (git-push-gh-daily-v2.sh, 0 6,18 UTC)

14. C0-BIS — KHÔNG BUÔNG PHAO CŨ KHI CHƯA CÓ PHAO MỚI

Off-site history KHÔNG về 0. Đo thật ở cả 2 đầu:

VPS  nuxt-repo       HEAD  2ea41583422427b6b5e6d886fa7a754198e121b7
GH   web-test        vps-daily-20260717  2ea41583422427b6b5e6d886fa7a754198e121b7   ✅ TRÙNG KHỚP

VPS  agent-data-repo HEAD  0ffd6778fc004d03620d68f89752ad5cb414d75b
GH   agent-data-test vps-daily-20260717  0ffd6778fc004d03620d68f89752ad5cb414d75b   ✅ TRÙNG KHỚP

+ GH giữ vps-daily-20260710…20260717 (8 nhánh, CLEANUP_DAYS=7) và main
+ 56 artifact Drive cũ/repo VẪN CÒN (~14 ngày) — mission này KHÔNG xoá

Phao mới có sẵn từ trước, nằm NGOÀI VPS, KHÔNG thuộc job Drive 4×/ngày đang sửa ⇒ điều kiện C0-BIS thoảKHÔNG cần HOLD_OFFSITE_GAP_OWNER_DECISION.

⚠️ Nói thẳng để Owner không hiểu nhầm: "phao" GitHub này chứa history có secret và bản thân nó cũng là một đường phát tán đang chạy 2×/ngày (ngoài phạm vi C0). C0-BIS cho phép điều đó ("bản thay thế vẫn chứa history-có-secret cũng được"), nhưng đây không phải một kho lưu trữ an toàn — chỉ là bằng chứng rằng gỡ .git khỏi Drive không làm mất bản off-site cuối cùng.

CLEAN_GIT_HISTORY_OFFSITE_BACKUP = TEMPORARILY_UNAVAILABLE_UNTIL_ROTATION_AND_HISTORY_SANITIZATION

(Mọi bản off-site history hiện có đều chưa sanitize. Bản sạch chỉ tạo được sau C2+C3.)

15. HISTORICAL DRIVE COPIES = REMAIN_UNCHANGED

web-test         remote_count = 56   (PRE 56)  ✅ không xoá
agent-data-test  remote_count = 56   (PRE 56)  ✅ không xoá
Drive Trash                            không đụng

16. GIT COMMIT & BUNDLE

commit  74459bdf223a117373a143cea8a82054fc27dcac
        fix(backup): exclude active-secret git history from Drive propagation
        1 file changed, 1 insertion(+)     ← scripts/code-backup-to-gdrive.sh

staged list = đúng 1 file · full staged diff đã đọc · --check CLEAN
secret scan staged diff = 0 hit · unrelated 15 M + 88 ?? giữ nguyên
branch feat/s177-sprint1-round-a · HEAD 74459bd · remote KHÔNG CÓ ⇒ GIT_PUSH=NO
POST bundle 22d550b1… · git bundle verify PASS

17. ROLLBACK

ssh contabo '/root/c0-checkpoint-20260717T130000Z/rollback/ROLLBACK.sh'

Thủ công:

CK=/root/c0-checkpoint-20260717T130000Z
install -m 0755 -o root -g root "$CK/backup/code-backup-to-gdrive.sh.PRE" \
  /opt/incomex/scripts/code-backup-to-gdrive.sh
sha256sum /opt/incomex/scripts/code-backup-to-gdrive.sh   # phải == 20762cfb…
bash -n /opt/incomex/scripts/code-backup-to-gdrive.sh

Git: cd /opt/incomex && git revert 74459bd (hoặc git checkout cdf25ec -- scripts/code-backup-to-gdrive.sh). ⚠️ Rollback ⇒ .git chảy lại lên Drive ngay ở lần chạy 08/12/15/20 ICT kế tiếp.

18. CONTAINER ZERO-MUTATION PROOF

Mission bắt đầu ~13:01 CEST (11:01 UTC). Mọi StartedAt đều TRƯỚC mốc đó ⇒ không container nào bị đụng.

incomex-agent-api-executor        1d841832a6fe  2026-06-04T08:46:26Z
incomex-agent-data                09188c72f327  2026-07-17T09:45:44Z   (== POST của A1 ✅)
incomex-claude-kb                 b1b40494fe7d  2026-07-16T03:25:22Z
incomex-claude-mcp                c7039e296421  2026-07-12T00:45:39Z
incomex-directus                  2245d86d2f6a  2026-05-08T09:13:06Z
incomex-nginx                     e50172b59a25  2026-06-30T00:44:14Z
incomex-nuxt                      42079c55bf9c  2026-07-17T03:49:53Z
incomex-qdrant                    2b5c328f0b4d  2026-03-18T07:03:47Z
pg-restore-test-20260520T031054Z  0cbc668c76f9  2026-05-20T03:10:55Z
postgres                          22540b78de8f  2026-04-17T05:35:18Z
uptime-kuma                       81d9f483b123  2026-04-08T09:18:38Z

11/11 running · agent-data RestartCount=0 · Health=healthy
balo bridge ActiveEnterTimestamp Fri 2026-07-17 01:32:28 CEST  (== A1 ✅ không restart)

19. REMAINING ROTATION / HISTORY-CLEANUP BACKLOG

🔴 C0-FOLLOWUP  Gỡ hardcode DIRECTUS_ADMIN_PASSWORD khỏi 2 file WORKTREE
                agent-data-repo/scripts/reconcile-{knowledge,tasks}.py
                → chừng nào còn, secret vẫn lên Drive 4×/ngày + GitHub 2×/ngày
                  DÙ .git đã bị loại. Đây là việc gấp nhất còn lại.  ← MỚI, do C0 phát hiện
C2  Rotate DIRECTUS_ADMIN_PASSWORD / ADMIN_TOKEN / PG / Qdrant / OpenAI
C3  Rewrite git history 2 file reconcile-*.py (nếu không, C2 không đóng được exposure)
    ⚠️ phải xử lý CẢ GitHub: 8 nhánh vps-daily-* + main đã mang history
A3  Owner quyết vector còn lại của nightly: TLS private key + PG dump + scripts/*
C4  Xoá 7 × .env.bak* + .env.mysql-backup
C5  rclone scope: drive → drive.file
C6  backup-to-gdrive.sh L31: Qdrant key vào argv mỗi đêm
—   Cân nhắc rclone crypt cho code-backup (hiện tar.gz trần)

20. EVIDENCE PATHS

CHECKPOINT  /root/c0-checkpoint-20260717T130000Z          (root:root 0700)
  backup/   code-backup-to-gdrive.sh.PRE · root-crontab.PRE · backup.sha256
  bundle/   PRE · POST (+ bundles.sha256)  — cả 2 verify PASS
  evidence/ containers.POST
  rollback/ ROLLBACK.sh (0700)
COMMIT      74459bd
LỆNH ĐO     crontab -l · cat/sed script · git rev-parse/status/ls-files/log/bundle verify
            · git worktree list · tar -tzf (list-only) · rclone lsf (read-only)
            · gh api .../git/matching-refs (GET only, từ laptop)
            · docker inspect --format (không đọc Env) · systemctl show

21. SECRET HYGIENE PROOF

✅ Không echo/print giá trị secret · không đưa vào argv (dùng tmpfs 0600 + grep -Ff <file>)
✅ Pattern tmpfs đã shred · /dev/shm CLEAN
✅ KHÔNG giữ bản sao secret mới: đã shred rclone.conf.PRE (chứa OAuth token, và không
   cần vì mission không sửa rclone config); đã xoá thư mục stage tạm chứa 2 file
   reconcile-*.py plaintext. Xác nhận: 0 file c0-verify-* còn lại.
✅ Không đọc payload Secret Manager · không đọc /proc/*/environ
✅ Report/KB/commit: 0 password/token/key

22. PROOF OF SCOPE

CONTAINER_MUTATION=NO
SERVICE_RESTART=NO
SECRET_VALUE_CHANGE=NO
SECRET_ROTATION=NO
SECRET_REVOKE=NO
HISTORICAL_BACKUP_DELETE=NO
GIT_HISTORY_REWRITE=NO
GIT_PUSH=NO
DB_WRITE=NO
DOCKER_CLEANUP=NO

Mutation đã thực hiện, đúng 1:

scripts/code-backup-to-gdrive.sh   +1 dòng (--exclude='.git')   commit 74459bd

23. FACTS NEXT AGENT MAY RELY ON

✅ code-backup-to-gdrive.sh POST sha256 = b47c5e36995582939ef55e261543747f3e4e8022893b20b81ac869e269d3ee27
✅ .git KHÔNG còn vào artifact Drive: web-test 7807→5529 · agent-data-test 1149→675
🔴 NHƯNG secret VẪN lên Drive 4×/ngày qua 2 file worktree reconcile-*.py — C0 KHÔNG đóng được
✅ .git file của worktree (nuxt-repo/web-rp-current/.git) được phủ bởi cùng 1 exclude basename
✅ remote_count = 56 == REMOTE_KEEP=56 ⇒ CHẠY MANUAL BACKUP SẼ XOÁ 1 artifact Drive cũ mỗi repo
✅ Off-site history KHÔNG về 0: GitHub vps-daily-20260717 khớp CHÍNH XÁC HEAD cả 2 repo
✅ git-push-gh-daily-v2.sh chạy `git add -A` + commit + force-push 2×/ngày (0 6,18 UTC)
✅ agent-data-repo/.env.working chứa Qdrant key CŨ (A1 §14) — không phải key live
✅ Wave A1 containment còn sống: backup-to-gdrive.sh L79 không còn -name ".env*"
✅ /opt/incomex vẫn KHÔNG remote; HEAD 74459bd; unrelated 15 M + 88 ?? nguyên vẹn

24. ASSUMPTIONS NEXT AGENT MUST NOT MAKE

❌ ĐỪNG đọc verdict "GIT_HISTORY_PROPAGATION_STOPPED" thành "secret hết rời VPS" — SAI
❌ ĐỪNG chạy code-backup-to-gdrive.sh thủ công để "kiểm tra" — retention sẽ XOÁ artifact Drive cũ
❌ ĐỪNG đo secret bằng regex theo TÊN BIẾN — probe theo tên cho 0 hit dù giá trị CÓ ở đó;
   phải đo theo GIÁ TRỊ, và đưa qua file tmpfs + `grep -Ff` (không bao giờ qua argv)
❌ ĐỪNG coi GitHub là nơi off-site an toàn — nó mang history có secret và tự phát tán 2×/ngày
❌ ĐỪNG rewrite history mà quên GitHub: 8 nhánh vps-daily-* + main đã có sẵn giá trị
❌ ĐỪNG loại reconcile-*.py khỏi backup như "cách nhanh" — là source thật, cần cho restore;
   cách đúng là gỡ hardcode + rotate (cần Owner)
❌ ĐỪNG chạy `git bundle verify` từ ngoài repo — báo "need a repository", không phải bundle hỏng

DỪNG sau báo cáo. Không tự chạy Mission A2.