DEV Community

Saint Zero Day
Saint Zero Day

Posted on

I Built a Tamper-Proof Security Blockchain Inside Notion

Notion MCP Challenge Submission 🧠

Everyone else built todo apps. We built a security blockchain inside Notion.

What I Built

SaintChain turns Notion into a tamper-proof security event ledger. Vulnerability scans, incident reports, compliance checks, policy violations -- every event gets cryptographically hashed into blocks, chained with SHA-256 double hashing, signed with Ed25519, and stored directly in Notion databases via MCP.

Modify a single character in any record. The chain breaks. SaintChain proves exactly where.

This is the same integrity architecture that NIST 800-171 and SOC 2 require for tamper-evident audit logging -- running inside Notion, orchestrated by Claude Code, with Solana-style checkpoint anchoring as a proof of concept.

The pitch: What if Notion wasn't just a wiki, but a block explorer for your security audit trail?

How It Works

  1. 13 security events (real CVEs, incidents, compliance checks, policy violations) get ingested and sealed into 3 blocks, plus a genesis block that bootstraps the chain
  2. Full chain verification -- double-SHA-256 hash linkage, Merkle root validation, Ed25519 signature checks on every block
  3. Tamper detection -- modify one hash and every downstream block breaks. SaintChain pinpoints the exact failure
  4. AI-driven forensics -- when tampering is detected, Claude writes a forensic incident narrative directly into Notion: what changed, what it means cryptographically, what to do about it
  5. Solana-style checkpoint anchoring -- chain state gets signed and recorded for third-party verification. This is a local simulation -- no live Solana transactions. I'd rather build that correctly with real Solana developers than ship something half-broken for a contest

The Notion Workspace

Three databases and four pages, all created and populated by Claude Code via MCP:

Component Purpose
Blocks DB 4 blocks with double-SHA-256 hashes, previous hashes, Merkle roots, Ed25519 signatures, verification status
Events DB 14 security events with type, severity, source, target, payload hash, block reference
Checkpoints DB Solana-style anchor point with chain hash, Merkle root, TX signature
Dashboard Chain health at a glance -- block count, event breakdown by type and severity
Verification Report (PASSED) Block-by-block integrity check -- all green
Incident Narrative CRITICAL alert -- forensic timeline of chain tampering at block 2
Verification Report (FAILED) Post-tamper report showing the exact failure point

SaintChain Audit Dashboard
Full SaintChain workspace -- 3 databases, 4 pages, all populated via MCP

Blocks database
Blocks database -- each row is a sealed block with its double-SHA-256 hash, previous hash, Merkle root, and event count

Events database
Events database -- 14 security events across scanning, incidents, compliance, and remediation. Color-coded severity selects.

Checkpoints database
Checkpoints database -- Solana-style anchor with chain hash, Merkle root, and TX signature

Security Dashboard
Security Dashboard -- chain health status, event breakdown by type, severity distribution

Verification Report PASSED
Verification Report -- all 4 blocks pass integrity checks

Incident Narrative
Tamper detected at block 2. CRITICAL callout, incident timeline, forensic analysis, and remediation steps -- all written by Claude into Notion.

Verification Report FAILED
Post-tamper verification -- block 2 FAILED with hash mismatch and invalid signature. Exact failure details shown.

Demo

The demo walks through the full pipeline:

  1. Generate Ed25519 keypair, create genesis block
  2. Ingest 13 security events across scanning, incident response, and remediation
  3. Batch events into 3 signed blocks with Merkle trees
  4. Run full chain integrity verification (passes)
  5. Sync all chain state to Notion databases and pages via MCP
  6. Tamper with block 2's hash (simulating direct database manipulation)
  7. Re-verify -- chain breaks at block 2, all downstream blocks invalidated
  8. Claude writes a forensic incident narrative into Notion
  9. Post-tamper verification report shows the exact failure point

Show us the Code

GitHub: saintzeroday/saintchain-notion

Architecture

saintchain/
├── crypto.py            # Ed25519 keys + SHA-256 double hashing (PyNaCl)
├── merkle.py            # Merkle tree construction + inclusion proofs
├── events.py            # Security event model, canonical JSON serialization
├── block.py             # Block model, double-SHA-256 hashing, chain linkage
├── chain.py             # Append-only ledger, verification, tamper detection
├── solana_anchor.py     # Solana checkpoint anchoring (local simulation)
├── notion_sync.py       # Chain state -> Notion database property payloads
├── notion_dashboard.py  # Dashboard page generator
└── notion_reporter.py   # Verification reports + incident narratives
Enter fullscreen mode Exit fullscreen mode

65 tests across 6 files. Crypto, Merkle trees, blocks, chain integrity, event serialization, and Notion payload generation all tested independently.

The Crypto Engine

Ported from Saint Blockchain, my Rust implementation of an immutable security event ledger.

Double SHA-256 -- hash the hash to prevent length-extension attacks. Same pattern Bitcoin uses:

def double_hash(data: bytes) -> str:
    first = hashlib.sha256(data).digest()  # raw 32 bytes, NOT hex
    return hashlib.sha256(first).hexdigest()
Enter fullscreen mode Exit fullscreen mode

Ed25519 signatures -- every block is signed. Deterministic signatures mean no nonce reuse risk:

def sign(self, data: bytes) -> str:
    signed = self._signing_key.sign(data)
    return signed.signature.hex()
Enter fullscreen mode Exit fullscreen mode

Merkle trees -- events within a block form a binary tree. The root hash commits to all events at once. You can prove a single event exists in a block without downloading the others:

def merkle_root(hashes: list[str]) -> str:
    if not hashes:
        return hash_str("empty")
    if len(hashes) == 1:
        return hashes[0]

    current_level = list(hashes)
    while len(current_level) > 1:
        next_level = []
        for i in range(0, len(current_level), 2):
            if i + 1 < len(current_level):
                combined = current_level[i] + current_level[i + 1]
            else:
                combined = current_level[i] + current_level[i]
            next_level.append(hash_str(combined))
        current_level = next_level
    return current_level[0]
Enter fullscreen mode Exit fullscreen mode

Chain Verification

The verification engine walks every block from genesis, checking three things:

  • Hash linkage -- does previous_hash match the prior block's hash?
  • Merkle root -- does the recomputed event tree match the stored root?
  • Ed25519 signature -- was this block signed by the chain authority?

When any check fails, verification stops and reports the exact failure:

def verify_chain(self, from_height: int = 0, to_height: int | None = None) -> ChainVerification:
    # ...
    for h in range(from_height, to + 1):
        block = self._blocks[h]

        if block.previous_hash != prev_hash:
            return ChainVerification(
                valid=False, blocks_checked=blocks_checked,
                first_invalid_height=h,
                error=f"previous hash mismatch at height {h}",
            )

        validation = block.validate(public_key)
        if not validation.merkle_valid:
            return ChainVerification(
                valid=False, blocks_checked=blocks_checked,
                first_invalid_height=h,
                error=f"invalid merkle root at height {h}",
            )
        if not validation.signature_valid:
            return ChainVerification(
                valid=False, blocks_checked=blocks_checked,
                first_invalid_height=h,
                error=f"invalid block signature at height {h}",
            )

        prev_hash = block.hash
        blocks_checked += 1

    return ChainVerification(valid=True, blocks_checked=blocks_checked)
Enter fullscreen mode Exit fullscreen mode

Five independent integrity checks per block -- hash, Merkle root, signature, event payload hashes, and event count. Any single modification anywhere in the chain cascades into a verification failure.

Notion Sync via MCP

The sync layer converts chain state into Notion database rows. Each block becomes a database entry with its hash, previous hash, Merkle root, signature, and real-time verification status:

def block_to_notion_properties(block: Block, verified: bool = True) -> dict:
    return {
        "Height": {"number": block.height},
        "Hash": {"rich_text": [{"text": {"content": block.hash[:32] + "..."}}]},
        "Previous Hash": {"rich_text": [
            {"text": {"content": (block.previous_hash[:32] + "...") if block.previous_hash else "genesis"}}
        ]},
        "Merkle Root": {"rich_text": [{"text": {"content": block.merkle_root[:32] + "..."}}]},
        "Timestamp": {"date": {"start": block.timestamp}},
        "Event Count": {"number": block.event_count},
        "Verified": {"checkbox": verified},
        "Signature": {"rich_text": [{"text": {"content": block.signature[:32] + "..."}}]},
    }
Enter fullscreen mode Exit fullscreen mode

The sync payload generator runs real-time validation on every block before pushing to Notion. If a block has been tampered with, its Verified checkbox shows up unchecked -- no manual flagging needed, the crypto does it.

When tamper detection fires, Claude generates a forensic incident narrative -- not a template fill, but a contextual analysis of what the hash change means, which blocks are affected, and what the response should be:

def generate_incident_narrative(chain: Chain, tampered_height: int, original_hash: str) -> dict:
    block = chain.get_block(tampered_height)
    current_hash = block.hash if block else "unknown"
    # Builds a Notion page with:
    # - CRITICAL callout block
    # - Numbered incident timeline (original hash -> modified hash -> detection -> cascade)
    # - Forensic analysis: what the hash change means cryptographically
    # - Remediation: isolate, restore from checkpoint, investigate, rotate keys, re-anchor
    # ...
Enter fullscreen mode Exit fullscreen mode

How I Used Notion MCP

Notion is not a display layer here. It is the block explorer.

The same way Solana Explorer lets you browse transactions on Solana, the SaintChain Notion workspace lets you browse a security audit blockchain:

  1. Blocks database = block explorer -- click any block to see its hash, previous hash, Merkle root, signature, event count, and verification status
  2. Events database = transaction ledger -- every security event is a row with type, severity, source, target, payload hash, and block reference
  3. Checkpoints database = anchor registry -- Solana-style checkpoints with TX signatures for third-party verification
  4. Dashboard page = chain health monitor -- overall chain status, event breakdown by type and severity
  5. Verification reports = integrity proofs -- block-by-block results that auditors can read without running code
  6. Incident narratives = AI forensics -- when tampering is detected, Claude writes the investigation report directly into Notion

Claude Code drives the entire pipeline: key generation, event signing, block sealing, chain verification, Notion database creation, data population, dashboard generation, tamper simulation, and incident narrative writing. It is not calling a single API endpoint. It is orchestrating cryptographic operations and reasoning about what the results mean.

Technical Stack

Layer Technology
Crypto SHA-256 double hash, Ed25519 (PyNaCl), Merkle trees
Chain In-memory append-only ledger with full verification
Anchor Solana-style checkpoint signing (local simulation -- not live)
Interface Notion databases + pages via MCP
Orchestration Claude Code
Tests 65 tests across 6 files

Why This Matters

SaintChain is not a hackathon toy. It is a piece of something I have been building for over a year.

I am building Zero Day Security (ZDS) -- a full security operations platform for small and mid-size organizations. Government contractors, healthcare providers, critical infrastructure operators -- they need real security tooling but cannot afford the enterprise stack. They are stuck choosing between "too expensive" and "nothing."

ZDS is 130,000 lines of Go. Vulnerability scanning, EDR integration (Wazuh, CrowdStrike, SentinelOne, Defender), attack path analysis, incident management, workflow automation, OSCAL compliance exports for NIST 800-171 and CMMC Level 2, threat intelligence feeds, external attack surface monitoring, identity threat detection. 62 packages, 1,163 tests. One engineer.

SaintChain is the audit backbone of that platform. Every scan result, every incident, every compliance check that flows through ZDS needs a tamper-proof record. When you submit a NIST 800-171 assessment, an auditor needs to know your logs have not been modified after the fact. That is what SaintChain does -- cryptographic proof that the record is intact.

The Notion MCP integration adds something ZDS does not have on its own: a universal interface anyone can read. The ZDS dashboard is built for security engineers. But compliance officers, executives, auditors, and clients need to see the audit trail too -- and they already use Notion. SaintChain via MCP puts integrity proofs where non-technical stakeholders can browse them without VPN access or training on a new tool.

And when something goes wrong -- when a record gets tampered with -- Claude does not just log a hash mismatch to stderr. It writes a forensic incident narrative in plain English, directly into Notion, explaining what changed, what it means, and what to do about it. That is the difference between a security alert nobody reads and an actionable response.

Every compliance framework requires this. NIST 800-171, SOC 2, CMMC, HIPAA, FedRAMP. Tamper-evident logging is not optional. SaintChain makes it real. Notion MCP makes it accessible.

Team

Solo build.

I'm Saint Zero Day -- GWOT veteran and security engineer, building Zero Day Security.

The cryptographic engine is ported from my Saint Blockchain Rust codebase. The Notion integration, orchestrator, dashboard, verification reports, and incident narratives are new for this challenge. The security domain knowledge -- the event types, the compliance frameworks, the incident response workflow -- comes from building ZDS and two decades of doing this for real.

Top comments (0)