# क्या encrypted audit log verification deletions का पता लगा सकती है?

Hash chain audit file के बीच से गायब record का पता लगा सकती है, लेकिन यह भरोसेमंद तरीके से नहीं बता सकती कि किसी ने अंत से records काट दिए हैं। यही अंतर वह जगह है जहाँ teams अक्सर verifier की क्षमता को बढ़ाकर बताती हैं और फिर incident के दौरान पता चलता है कि उन्होंने integrity का evidence तो बचाया, लेकिन history नहीं।

Encryption इस जवाब को नहीं बदलता। यह records के contents को सुरक्षित रखती है। Encrypted bytes पर बनी chain यह दिखा सकती है कि वे bytes अब भी अपेक्षित तरीके से जुड़े हुए हैं, भले ही verifier चलाने वाला कोई व्यक्ति उन्हें decrypt न कर सके। यह साबित नहीं कर सकती कि file में कभी मौजूद रहे हर record शामिल है, जब तक file के बाहर का कोई evidence उसकी history के किसी बाद के बिंदु को याद न रखता हो।

NIST hash chain को append-only structure बताता है, जिसमें हर block में पिछले data का hash शामिल होता है। इसलिए किसी block को बदलने पर उसके successor में दर्ज digest भी बदल जाता है। यह tamper evidence है, time machine नहीं।

## Chain verification relationship जाँचती है, completeness नहीं

Verifier यह जाँचती है कि हर record सही predecessor की ओर संकेत करता है और हर record digest उसके stored bytes से मेल खाता है या नहीं। Trusted first record मिलने पर यह उन सभी records के बीच continuity स्थापित कर सकती है जो उसके बाद अब भी मौजूद हैं।

मान लीजिए file में records 1 से 100 तक हैं। Record 58 में record 57 का digest है और record 59 में record 58 का digest है। अगर कोई record 58 हटा दे और बाकी सब कुछ वैसा ही रहने दे, तो record 59 अब भी ऐसे digest की ओर संकेत करेगा जिसे verifier खोज नहीं सकती। Verification gap पर fail हो जाएगी।

अब records 91 से 100 तक हटा दें। Record 90 अब भी record 89 की ओर सही संकेत करता है। छोटी की गई file के भीतर कुछ भी यह नहीं बताता कि record 90 के बाद कभी दस और records थे। जो verifier record 1 से शुरू होकर file के अंत पर रुक जाती है, वह success लौटा सकती है। उसने प्रस्तुत की गई history को verify किया। उसने यह verify नहीं किया कि प्रस्तुत history पूरी थी।

लोग अक्सर तीन अलग-अलग दावों को «log tamper-proof है» में मिला देते हैं:

- बचे हुए records बदले नहीं गए हैं।
- बीच से कोई record नहीं हटाया गया।
- File वहीं खत्म होती है जहाँ original history खत्म हुई थी।

इन दावों के लिए अलग-अलग evidence चाहिए। Linear chain पहले दावे को अच्छी तरह संभालती है और दूसरे के सरल रूप का पता लगा लेती है। तीसरे के लिए बाद में किया गया independent commitment चाहिए, जिसे अक्सर checkpoint, head, seal, receipt या witness कहा जाता है।

Certificate Transparency अलग data structure के साथ यही विभाजन दिखाता है। RFC 9162 के अनुसार Merkle consistency proof यह साबित कर सकती है कि नया tree, पहले बताए गए tree के मुकाबले append-only है। वहाँ पहले से बताया गया tree head असली काम करता है। उसके बिना current root आपको उन records के बारे में कुछ नहीं बताता जिन्हें log operator ने आपको दिखाने का फैसला नहीं किया।

## ऐसा test log बनाएँ जो payloads को opaque ciphertext माने

Production audit file पर अभ्यास न करें। एक disposable fixture बनाएँ, जिसके payloads verifier को ciphertext जैसे दिखें, और फिर exact byte records के खिलाफ deletion test करें।

नीचे दी गई script JSON Lines file लिखती है। हर record में sequence number, opaque base64 payload, predecessor digest और अपना digest होता है। Payload जानबूझकर random test data है, real encryption नहीं। इस test के लिए इतना पर्याप्त है, क्योंकि chain verifier को केवल स्थिर opaque bytes चाहिए। Real encrypted log में इसकी जगह actual serialized ciphertext record और उसका authenticated metadata दें।

```python
# make_log.py
import base64
import hashlib
import json
import os

OUT = "audit.jsonl"
ZERO = "0" * 64


def canonical(record):
    return json.dumps(record, sort_keys=True, separators=(",", ":")).encode()


def digest(record):
    return hashlib.sha256(canonical(record)).hexdigest()

prev = ZERO
with open(OUT, "w", encoding="utf-8") as f:
    for seq in range(1, 13):
        body = {
            "seq": seq,
            "ciphertext": base64.b64encode(os.urandom(24)).decode(),
            "prev": prev,
        }
        body["hash"] = digest(body)
        f.write(json.dumps(body, sort_keys=True) + "\n")
        prev = body["hash"]

print(f"wrote 12 records to {OUT}")
print(f"head seq=12 hash={prev}")
```

Writer पर भरोसा करने के बजाय अलग verifier का इस्तेमाल करें। उसे duplicate sequence numbers, unexpected sequence jumps, malformed records, गलत predecessor references और गलत hashes को reject करना चाहिए। केवल hashes जाँचने वाली verifier में blind spot होता है, क्योंकि attacker पर्याप्त associated fields को फिर से व्यवस्थित करके reordered file स्वीकार करवा सकता है।

```python
# verify_log.py
import hashlib
import json
import sys

ZERO = "0" * 64


def canonical(record):
    return json.dumps(record, sort_keys=True, separators=(",", ":")).encode()


def digest_without_hash(record):
    copy = dict(record)
    supplied = copy.pop("hash", None)
    return supplied, hashlib.sha256(canonical(copy)).hexdigest()


def fail(message):
    print(f"FAIL {message}")
    raise SystemExit(1)

path = sys.argv[1]
prev = ZERO
expected_seq = 1
last_hash = ZERO

with open(path, encoding="utf-8") as f:
    for line_no, line in enumerate(f, start=1):
        try:
            record = json.loads(line)
        except json.JSONDecodeError:
            fail(f"line={line_no} invalid JSON")

        if record.get("seq") != expected_seq:
            fail(f"line={line_no} expected_seq={expected_seq} got={record.get('seq')}")

        if record.get("prev") != prev:
            fail(f"line={line_no} seq={expected_seq} predecessor mismatch")

        supplied, calculated = digest_without_hash(record)
        if supplied != calculated:
            fail(f"line={line_no} seq={expected_seq} digest mismatch")

        prev = supplied
        last_hash = supplied
        expected_seq += 1

print(f"OK records={expected_seq - 1} head={last_hash}")
```

Fixture चलाएँ और reported head को log file के बाहर सुरक्षित रखें:

```text
python3 make_log.py
python3 verify_log.py audit.jsonl
```

Success का रूप कुछ ऐसा होना चाहिए, हालाँकि हर run में digest अलग होगा:

```text
wrote 12 records to audit.jsonl
head seq=12 hash=8f...c2
OK records=12 head=8f...c2
```

आखिरी `records=12` और `head=...` की जोड़ी आपका checkpoint है। Copies में बदलाव करने से पहले इसे अलग test-notes file में रखें। अगर checkpoint केवल उसी file में छोड़ दिया जिसे आप attack करने वाले हैं, तो आपने attacker के लिए edit करने योग्य record तैयार कर दिया है।

## Tail हटाने पर सीमा तुरंत दिख जाती है

Tail deletion वह test है जो «complete» शब्द के इस्तेमाल को लेकर सावधान करना चाहिए। File की copy बनाएँ, आखिरी तीन lines हटाएँ और वही verifier चलाएँ।

```text
cp audit.jsonl tail-cut.jsonl
head -n 9 audit.jsonl > tail-cut.jsonl
python3 verify_log.py tail-cut.jsonl
```

Verifier नौ records के लिए success report करेगी। उसे ऐसा ही करना चाहिए। Records 1 से 9 अब भी valid chain बनाते हैं। इसे verifier की failure कहना एक खतरनाक design को बढ़ावा देगा, जिसमें valid partial histories corrupt दिखाई देंगी।

अब उसके result की तुलना checkpoint से करें:

```text
OK records=9 head=4a...91
expected records=12 head=8f...c2
```

अब आपके पास truncation का evidence है। Proof छोटी की गई file के internal links से नहीं, बल्कि उस evidence से आता है जिसे log के लंबा होने पर capture किया गया था और जिससे अब असहमति दिख रही है।

Sequence number human diagnosis में मदद करता है। अपने आप यह security नहीं बनाता। जो attacker tail rewrite कर सकता है, वह final sequence number को 12 से 9 कर सकता है। Sequence number तब उपयोगी होता है जब expected value ऐसे source से आई हो जिसे attacker rewrite न कर सके, जैसे अलग service में रखा signed checkpoint, protected release artifact या किसी दूसरे administrative domain तक पहुँचा export।

कई audit designs इस test को बहुत obvious समझकर छोड़ देते हैं। फिर भी destructive action के बाद यही failure सबसे महत्वपूर्ण हो सकता है। Malicious process को history को internally inconsistent बनाने की जरूरत नहीं होती। उसे केवल history को इतना पहले रोकना होता है कि action छिप जाए।

## Middle deletion fail होनी चाहिए, लेकिन केवल बताई गई conditions में

एक और copy बनाएँ और ऐसा record हटाएँ जिसके predecessor और successor दोनों हों। Record 6 अच्छा target है।

```text
cp audit.jsonl middle-cut.jsonl
sed '6d' audit.jsonl > middle-cut.jsonl
python3 verify_log.py middle-cut.jsonl
```

Result का रूप ऐसा होना चाहिए:

```text
FAIL line=6 expected_seq=6 got=7
```

अगर sequence check हटा दें, तो verifier एक test बाद fail होगी, क्योंकि record 7 में record 6 का digest है, जबकि verifier के पास record 5 का digest है। दोनों checks रखें। Sequence mismatch operator को समस्या साफ दिखाता है, जबकि predecessor mismatch उस cryptographic relationship को दिखाता है जो टूटा है।

यह मजबूत दावा न करें कि hash chain हमेशा middle deletion का पता लगा लेती है। यह simple deletion का पता तब लगाती है जब attacker बची हुई chain को rewrite न कर सके। Public digest algorithm से कोई भी नए hashes निकाल सकता है। अगर कोई record 6 हटा सकता है, record 7 का predecessor field बदल सकता है, record 7 का hash फिर से बना सकता है और यही काम record 12 तक कर सकता है, तो rebuilt file अपने नए head के खिलाफ verify हो सकती है।

यह collision attack या SHA-256 में break नहीं है। यह सामान्य recomputation है। System ने नई history स्वीकार कर ली, क्योंकि उसके पास पुराने history के मौजूद होने का protected evidence नहीं था।

Digital evidence पर NIST की guidance operational point को साफ तौर पर रखती है: stored hash ऐसी जगह रहना चाहिए जहाँ evidence तक पहुँच रखने वाले लोग उसे बदल या overwrite न कर सकें। उसी report में hash chains को hashes सुरक्षित करने के लिए उपयोगी बताया गया है, लेकिन reference value का external storage फिर भी ज़रूरी रहता है।

इसलिए middle deletion test के दो रूप होने चाहिए:

1. एक line हटाएँ और बाद के bytes को जस का तस रखें। Chain fail होनी चाहिए।
2. एक line हटाएँ और बाद के हर digest को फिर से बनाएँ। Rebuilt chain को independently retained checkpoint के खिलाफ fail होना चाहिए।

अगर आपका test plan केवल पहला version चलाता है, तो वह file damage और लापरवाह tampering को test करता है। वह ऐसे attacker को test नहीं करता जो log storage में लिख सकता है।

## Encryption और chain integrity अलग सवालों के जवाब देते हैं

Encrypted records काम को दो हिस्सों में बाँट देती हैं। Operators ciphertext पर chain verify कर सकते हैं, बिना secrets, request bodies, command outputs या अन्य sensitive audit content पढ़े। उचित access वाले investigators बाद में relevant records decrypt कर सकते हैं। जब audit data खुद sensitive हो, तब यह समझदारी भरा design है।

लेकिन encrypted bytes अपने origin को certify नहीं करते। Ciphertext record हटाया जा सकता है। Encryption और logging path को नियंत्रित करने वाला व्यक्ति नया ciphertext record जोड़ सकता है। अगर encryption mode data authentication नहीं देती, तो कभी-कभी ciphertext को इस तरह बदला जा सकता है कि decryptor को यह पता न चले कि वजह क्या थी। Record confidentiality और integrity के लिए authenticated encryption इस्तेमाल करें, फिर records को ordered history में बाँधने के लिए chain का इस्तेमाल करें।

Design और incident report लिखते समय इन checks को अलग रखें:

- Record authentication पूछता है कि एक encrypted record बदला गया या नहीं।
- Chain verification पूछती है कि हर बचा हुआ record claimed predecessor के बाद आता है या नहीं।
- Checkpoint comparison पूछता है कि देखी गई history पहले देखे गए head तक पहुँचती है या नहीं।
- Event capture पूछता है कि system ने action को log में लिखा भी था या नहीं।

आखिरी सवाल असहज है। कोई cryptographic log यह साबित नहीं कर सकती कि event record हुआ था, अगर compromised writer ने record emit ही नहीं किया। Network service, host audit facility या human approval event जैसी independent boundaries से evidence इकट्ठा करके इस exposure को कम किया जा सकता है। Local log को append-only कह देने से यह gap खत्म नहीं होता।

Sallyport का audit model इस संदर्भ में उपयोगी है, क्योंकि उसके agent-run और individual-call journals एक encrypted, hash-chained audit log से आते हैं, और `sp audit verify` vault key के बिना ciphertext chain को offline verify कर सकता है। इससे reviewer credentials या audit trail द्वारा सुरक्षित action data उजागर किए बिना continuity जाँच सकता है।

## Checkpoint को rewrite करना कठिन होना चाहिए, केवल copy किया हुआ नहीं

Checkpoint किसी समय log के बारे में दिया गया statement है। कम से कम इसमें log identifier, final sequence number, final digest, creation time और format या algorithm version शामिल करें। इसे अपनी custody वाले अलग artifact की तरह रखें।

Test harness के लिए यह छोटा format पर्याप्त है:

```json
{
  "log_id": "agent-actions-test-a",
  "sequence": 12,
  "head": "8f...c2",
  "created_at": "2026-07-22T14:30:00Z",
  "format": "jsonl-chain-v1"
}
```

`created_at` पर अकेले भरोसा न करें। Local clock sorting और investigation के लिए उपयोगी है, लेकिन machine नियंत्रित करने वाला attacker उस clock और उसे रखने वाली file को भी नियंत्रित कर सकता है। Checkpoint को ताकत इस बात से मिलती है कि वह कहाँ गया और उसे कौन बदल सकता है।

एक practical arrangement में एक party audit records लिखती है और दूसरी party periodic heads सुरक्षित रखती है। इस दूसरी party को decryption access की जरूरत नहीं है। उसे केवल इतनी जानकारी चाहिए कि वह बाद की ऐसी file reject कर सके जिसका final digest और sequence पहले देखे गए values से मेल न खाए।

Developer tool के लिए independent party बहुत बड़ी व्यवस्था नहीं होनी चाहिए। Protected CI artifact, release-attestation system, separate collector account या किसी दूसरे administrator द्वारा approved daily export पर्याप्त हो सकता है। Higher-risk actions के लिए action के तुरंत बाद checkpoint emit करें और उसे workstation के बाहर रखें। Cadence उस damage window के अनुसार तय करें जिसे आप स्वीकार कर सकते हैं। रोज़ का checkpoint yesterday's head और आज की missing tail के बीच के घंटों के लिए completeness साबित नहीं कर सकता।

जब verifiers को यह जानना हो कि checkpoint किसने जारी किया, तब signing मदद करती है। Signature checkpoint data को signing authority से बाँधती है, लेकिन signer को honest नहीं बनाती। अगर वही compromised process audit log लिखता और replacement checkpoints sign करता है, तो trust boundary अब भी एक ही है। Witness को ऐसी जगह रखें जहाँ original writer उसे चुपचाप command न दे सके।

## Write-blind storage उस attacker को बदल देती है जिसे आप test कर रहे हैं

ऐसी जगह store की गई chain जहाँ writer पुराने records को खुलकर edit कर सकता है, उस chain से अलग threat model रखती है जो write-blind append path से store होती है। पहले design को history rewrite पकड़ने के लिए external witnesses चाहिए। दूसरा design शुरुआत में ही writer को ऐसी rewriting करने से रोकने की कोशिश करता है।

«Write-blind» शब्द का इस्तेमाल सटीक रूप से करें। इसका अर्थ होना चाहिए कि नया record submit करने वाला component prior encrypted log records को अपनी इच्छा से पढ़ या mutate न कर सके। इसका अर्थ यह नहीं है कि disk fail नहीं हो सकती, administrator file delete नहीं कर सकता या operating system compromise नहीं हो सकता। यह केवल एक attack path को सीमित करती है: history देखकर उसके चुने हुए हिस्से को बाद में rewrite करना।

यह अंतर आपके test cases को प्रभावित करता है। Ordinary file access के लिए deletion plus rehashing test करें, क्योंकि attacker data पढ़कर hashes निकाल सकता है। Write-blind store के लिए देखें कि caller deletion, replacement, rollback या उसी identity के तहत दूसरी log माँग सकता है या नहीं। यह भी test करें कि verifier intended log को कैसे पहचानती है। गलत file की perfect chain फिर भी गलत evidence है।

यहाँ सबसे आम गलती storage permissions को cryptographic anchor समझना है। पर्याप्त privilege वाला attacker permissions बदल सकता है। Permissions फिर भी महत्वपूर्ण हैं, क्योंकि वे casual damage घटाती हैं और यह सीमित करती हैं कि कौन-से processes काम कर सकते हैं। लेकिन वे ऐसे checkpoint की जगह नहीं लेतीं जो ऐसी boundary के पार गया हो जिसे attacker नियंत्रित न करता हो।

## Rollback को truncation से अलग test करें

Rollback, tail truncation जैसा दिखता है, लेकिन इसमें plausible पुरानी file होती है। Crash, backup restore या जानबूझकर replacement के बाद machine कल की valid audit file restore कर सकती है। Restored file internal verifier पास कर सकती है, क्योंकि वह सचमुच कल valid थी।

अगर checkpoint में नया head दर्ज है, तो वह rollback पकड़ लेगा। Candidate file की तुलना उस सबसे नए retained checkpoint से करें, न कि संयोग से मिलने वाले सबसे पुराने checkpoint से। Chain verifier खुद यह नहीं बता सकती कि कौन-सा valid version current होना चाहिए।

इसे सीधे test करें:

1. `audit.jsonl` और उसका 12-record checkpoint सुरक्षित रखें।
2. अधिक records वाली दूसरी fixture file बनाएँ और उसका नया checkpoint रखें।
3. नई file को 12-record copy से replace करें।
4. Restored file को पहले internally verify करें, फिर नए checkpoint से compare करें।

दो अलग results की अपेक्षा रखें। Internal verification सफल होनी चाहिए। Checkpoint comparison fail होनी चाहिए, क्योंकि file पुराने sequence और digest पर खत्म होती है। अगर दोनों checks fail हों, तो संभव है fixture में bytes बदल गए हों और rollback reproduce न हुआ हो। अगर दोनों सफल हों, तो आप गलत checkpoint से compare कर रहे हैं या आपने checkpoint सुरक्षित ही नहीं रखा।

पुरानी file में fresh records append करके rollback को «ठीक» न करें। पहले state सुरक्षित करें। Append करने के बाद संभावित missing history और incident के बाद की activity मिल जाती है। Recorded incident boundary के साथ नया log segment शुरू करें, या अपने organization की पहले से approved retention और recovery procedure अपनाएँ।

## Investigation के दौरान verifier results को उपयोगी बनाएँ

केवल `invalid` लौटाने वाली verifier अनावश्यक काम बढ़ाती है। उसे sensitive record payloads print किए बिना पहली failed condition बतानी चाहिए। Sequence number, byte offset या line number, expected predecessor digest, observed predecessor digest और calculated record digest आमतौर पर पर्याप्त detail देते हैं।

Error message में decrypted content log करने से बचें। Verification tool अक्सर CI, support bundle या terminal capture में चलती है। अगर encrypted audit design verification fail होने पर cleartext leak करती है, तो integrity incident data exposure में बदल जाता है।

Failed verification पर disciplined response का क्रम छोटा होना चाहिए:

- Original file सुरक्षित रखें और उस preserved copy का digest दर्ज करें।
- Newest checkpoint, prior checkpoints और external exports इकट्ठे करें।
- Copies पर verification चलाएँ और exact command output सुरक्षित रखें।
- Reported head की तुलना हर retained checkpoint से करें।
- तय करें कि failure corruption, middle gap, tail loss, rollback या witness से conflict करने वाली rewritten chain है।

Classification महत्वपूर्ण है। Middle-gap failure बताती है कि file में internal contradiction है। Valid लेकिन छोटी chain बताती है कि file शायद केवल अपने आखिरी record तक complete है। Valid chain का बाद के checkpoint से disagree करना rollback, truncation या substitution का evidence देता है। Rewritten chain internally साफ दिख सकती है, लेकिन पुराने independent head से disagree करेगी।

Evidence जितना support करता है, उससे अधिक promise न करें। «Audit chain sequence 90 तक verify हुई और retained sequence 100 checkpoint से मेल नहीं खाती» मजबूत और specific statement है। «किसी ने log नहीं बदली» ऐसा statement नहीं है।

## असली test वही है जो आपकी actual boundary के खिलाफ हो

सबसे पहले disposable line-deletion tests चलाएँ, क्योंकि वे mechanics समझाते हैं। फिर वही cases real audit export और उसके real verifier पर चलाएँ, copies और format की documented manipulation boundaries का इस्तेमाल करते हुए। केवल यह देखने के लिए कि क्या होता है, production encrypted log को in place edit न करें।

Sallyport के लिए original encrypted log सुरक्षित रखें और हर controlled alteration से पहले और बाद में copy पर `sp audit verify` चलाएँ। दर्ज करें कि verifier internal gap पकड़ती है या नहीं, छोटी की गई copy अब भी verify होती है या नहीं, और retained head छोटी history को उजागर करता है या नहीं। तीनों के जवाब इस अस्पष्ट दावे से अधिक उपयोगी हैं कि log tamper-evident है।

Valid chain का अर्थ है कि जाँचे गए records एक-दूसरे से सहमत हैं। Valid chain plus trusted later checkpoint का अर्थ है कि records history के एक ज्ञात बिंदु तक पहुँचते हैं। जब missing actions महत्वपूर्ण हों, तो दूसरे statement के लिए design और test करें।
