π Stop "Trusting." Start Proving.
A Look Under the Hood of Flowork's Crypto-Secure Automation. (This Ain't Your Grandma's Audit Log).
Let's paint a picture.
It's Monday morning. You're barely halfway through your coffee when a frantic message lands from the finance team. "Yo, that billing workflow from six months ago? It might've screwed up a huge batch of invoices. We need you to pull the exact logic that ran at 2:15 AM on October 27th. Like, now."
If you're using a typical cloud automation tool, this is the "oh crap" moment. You can probably find the current workflow. You might even have a "version history" that says, "Admin User updated this." But can you mathematically prove that the version you're looking at is the exact, unaltered code that executed? Can you prove it to a pissed-off auditor? What if a rogue admin (or a hacker) just... edited the logs?
This is the dirty little secret of most platforms: their audit logs are built on "trust me, bro." They're just entries in a database that can be changed. That's fine for fluff, but it's a nightmare for compliance, security, and proving you didn't mess up (called "non-repudiation").
But what if there was another way? What if, instead of a flimsy "trust me" log, you had a "prove it" cryptographic receipt for every... single... change... ever made?
That's the core idea we're about to rip apart. We're diving head-first into the source code of Flowork to show how it's built from the ground up to create workflows that aren't just "logged," but are cryptographically auditable and provably secure.
This isn't just a "better Zapier." This is a fundamentally different beast.
ποΈ The Foundation: Why a Hybrid Model is Your First Big Win
Before we even whisper the word "crypto," we gotta talk architecture.
Your typical automation-as-a-service (like Zapier or Make) is a black box. You hand over your sensitive dataβcustomer info, API keys, the secret family recipeβto their cloud, and they run it on their servers. You have zero control. You just... hope.
Flowork's model is hybrid. You get a slick, modern UI in the cloud to design your workflows, but the executionβthe actual workβhappens on a self-hosted "Core" engine that runs on your hardware (your laptop, a server, a Docker container).
This isn't a "nice-to-have" feature; it's a game-changer for data privacy. Don't believe me? Let's look at the code.
Code Deep Dive 1: The docker-compose.yml Blueprint
This is the architectural proof, not marketing fluff.
# A simplified look at C:\FLOWORK\docker-compose.yml
services:
# The Gateway handles the connection to the cloud UI
flowork_gateway:
image: flowork/gateway:dev
...
# The Core is YOUR engine. It does all the work.
flowork_core:
image: flowork/core:dev
...
volumes:
# This is the magic.
# Your local folders are mounted directly into the engine.
- ./data:/app/data
- ./modules:/app/flowork_kernel/modules
- ./plugins:/app/flowork_kernel/plugins
- ./tools:/app/flowork_kernel/tools
- ./ai_models:/app/flowork_kernel/ai_models
- ./assets:/app/flowork_kernel/assets
...
volumes:
flowork_data:
driver: local
driver_opts:
device: './data' # Your database lives here, on your machine.
Look at those volumes. Your database (./data), your custom code (./modules), and even your local AI models (./ai_models) are all mounted straight from your local filesystem.
When your workflow runs, it's not shipping your customer list off to a server in God-knows-where. It's processing customer.csv right off your hard drive. This is especially critical for AI. Running local, self-hosted AI models is the only way to use the power of LLMs without facing a "legal time bomb" of data privacy violations.
But the real genius? The flowork_core initiates an outbound WebSocket connection to the gateway. This means your engine can live behind a crazy-restrictive corporate firewall with zero open inbound ports.
You get the convenience of a cloud UI with the security of a paranoid, air-gapped network. Chef's kiss. π
π Pillar 1: Killing the Password with Crypto-Identity
Okay, so your data is safe on your machine. But what about you? Your identity?
Flowork's next move is to kill the password. Passwords suck. They get stolen, leaked, and phished.
Instead, your identity is a cryptographic keypair:
-
Your Private Key: A secret file (
0x...) that lives only on your machine. This is your new password. You use it to "sign" messages, proving you are you. -
Your Public Address: A public ID (
0x...) made from your private key. This is your new username.
The server only ever knows your public address.
Code Deep Dive 2: The "Birth" of Your Key
When you set up Flowork, it doesn't just add a row to a users table. It forges a real cryptographic asset.
# A look at C:\FLOWORK\generate_env.py
GUI_KEY_FILE_NAME = "DO_NOT_DELETE_private_key.txt"
def _gen_secret(length: int = 32) -> str:
# 32 bytes = 64 hex chars = 256 bits
return secrets.token_hex(length)
def write_gui_login_key(data_dir: Path, private_key: str):
"""
(English Hardcode) Write the DO_NOT_DELETE_private_key.txt file for the GUI.
(English Hardcode) This is read by the .bat scripts to show the user.
"""
key_file_path = data_dir / GUI_KEY_FILE_NAME
# ... content omitted for brevity ...
key_file_path.write_text("\n".join(content), encoding="utf-8")
def main(argv):
#... (setup code)...
# Check if a key already exists
if _should_rotate("ENGINE_OWNER_PRIVATE_KEY") and not gui_key_to_inject:
print("[info] Generating new ENGINE_OWNER_PRIVATE_KEY.")
# This is the "birth" of your key
new_key = "0x" + _gen_secret(32)
new_env["ENGINE_OWNER_PRIVATE_KEY"] = new_key
#... (more setup)...
# This writes the key to the .txt file so you can find it
write_gui_login_key(data_dir, new_env.get("ENGINE_OWNER_PRIVATE_KEY"))
Code Deep Dive 3: Your Treasure Map
"Cool, a key. Where is it?" Flowork's run script is your friendly treasure map.
@echo off
rem A snippet from C:\FLOWORK\3-RUN_DOCKER.bat
echo --- MENCARI PRIVATE KEY ANDA... ---
echo.
echo Your Login Private Key should appear below (inside the warning box):
echo.
set "KEY_FILE_PATH=%~dp0\data\DO_NOT_DELETE_private_key.txt"
if exist "%KEY_FILE_PATH%" (
echo [INFO] Reading key from saved file: %KEY_FILE_PATH%
echo.
echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
echo !!! YOUR LOGIN PRIVATE KEY IS:
echo.
TYPE "%KEY_FILE_PATH%"
echo.
echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
) else (
echo Key file not found at %KEY_FILE_PATH%
)
You take this 0x... key and paste it into the login screen. But here's the magic: THAT KEY NEVER LEAVES YOUR BROWSER.
Code Deep Dive 4: The "Crypto Handshake"
Your browser doesn't send the key. It uses it to sign a challenge.
The Browser (Client-Side):
// A simplified look at C:\FLOWORK\flowork-gui\template\web\src\store\auth.js
import { ethers } from 'ethers';
async function loginWithPrivateKey(key) {
try {
// 1. Load the key into memory. It NEVER leaves the browser.
const wallet = new ethers.Wallet(key);
// 2. Get a one-time "challenge" from the server.
const challenge = await apiGetLoginChallenge(wallet.address);
// 3. Use the private key to SIGN the challenge.
// The key itself is NOT sent.
const signature = await wallet.signMessage(challenge);
// 4. Send your public ID, the challenge, and the signature.
const profile = await apiGetProfile(wallet.address, challenge, signature);
// You're in!
} catch (error) {
console.error("Login failed:", error);
}
}
The Server (Crypto-Firewall):
The server now plays "Guess Who?" with cryptography.
# A look at C:\FLOWORK\flowork-gateway\app\helpers.py
from eth_account.messages import encode_defunct
from web3.auto import w3
def _verify_signature(expected_address, message, signature):
"""
(English Hardcode) Verify that the signature was created by the owner
(English Hardcode) of the expected_address.
"""
try:
# 1. Re-create the exact same challenge message
message_hash = encode_defunct(text=message)
# 2. This is the magic. Recover the public address from ONLY
# the message and the signature.
recovered_address = w3.eth.account.recover_message(
message_hash, signature=signature
)
# 3. Check if the signer is who they say they are.
if recovered_address.lower() == expected_address.lower():
return True # Access Granted
except Exception as e:
current_app.logger.warning(f"Signature verification failed: {e}")
return False # Access Denied
This is the payoff: Your server's users table only has public addresses. An attacker who steals your entire gateway database gets... nothing. They have a list of public usernames, but they don't have the private keys. They can't log in. They can't sign anything.
A full database breach becomes a non-event for credential theft.
π Pillar 2: The "Flowchain" (Your Unbreakable Alibi)
So, we've proven who you are. Now, let's prove what you did.
This is where Flowork's "Flowchain" comes in. When you save a workflow, you're not just overwriting a file or updating a row. You are committing a new, signed, and chained version to an append-only log.
Each workflow lives in its own folder (.../data/presets/My-Workflow/) and its history (v1_...json, v2_...json) acts as a mini-blockchain.
Code Deep Dive 5: Building the Chain
Let's look at the exact code that runs when you hit "Save."
# A simplified look at C:\FLOWORK\flowork-core\flowork_kernel\services\preset_manager_service\preset_manager_service.py
import json
import hashlib
import os
class PresetManagerService(BaseService):
def get_latest_version_hash(self, workflow_dir: str) -> str | None:
#... (finds the latest v_...json file and returns its hash)
# For this example, let's say it returns "hash_of_v2"
pass
def _calculate_hash(self, file_path):
#... (calculates the SHA256 hash of a file)
pass
def save_preset(self, preset_name: str, workflow_data: dict,
author_id: str, signature: str):
workflow_dir = os.path.join(self.presets_path, preset_name)
os.makedirs(workflow_dir, exist_ok=True)
# 1. Find the hash of the *previous* version (e.g., "hash_of_v2")
previous_hash = self.get_latest_version_hash(workflow_dir)
# 2. Get the next version number (e.g., 3)
next_version = self.get_next_version_number(workflow_dir)
new_version_path = os.path.join(workflow_dir, f"v{next_version}_...json")
# 3. Create the new version "block"
version_data = {
"version": next_version,
"timestamp": int(time.time()),
"previous_hash": previous_hash, # <-- CHAINS to v2
"author_id": author_id, # <-- PROVES who
"signature": signature, # <-- PROVES authenticity
"workflow_data": workflow_data # <-- The actual workflow
}
# 4. Save the new version file (v3_...json)
with open(new_version_path, 'w', encoding='utf-8') as f:
json.dump(version_data, f, indent=4)
# 5. Calculate the hash of this new file (e.g., "hash_of_v3")
current_hash = self._calculate_hash(new_version_path)
# 6. Update the main preset file to point to this new "head"
main_preset_file = os.path.join(workflow_dir, "preset.json")
#... (save current_hash to main_preset_file)
This is the whole system. When you save Version 3, its file contains the hash of Version 2. Version 2's file contains the hash of Version 1.
You've just built a chain of evidence.
Code Deep Dive 6: The Unblinking Auditor Bot
Now, how do you "audit" this? You run the verifier. This "Auditor Bot" script just walks the chain and checks the receipts.
# A look at C:\FLOWORK\flowork-core\flowork_kernel\utils\flowchain_verifier.py
import json
import os
import hashlib
from eth_account.messages import encode_defunct
from web3.auto import w3
#... (calculate_hash function is here)...
def verify_workflow_chain(workflow_directory):
"""
Verifies the entire history chain of a workflow, from the
newest version down to the first.
"""
# 1. Get all version files (v1, v2, v3...) and sort them
files = sorted(
[f for f in os.listdir(workflow_directory) if f.endswith('.json') and f.startswith('v')],
# ... (key omitted for brevity)
)
previous_file_hash = None
# 2. Loop through the chain from v1 -> v2 -> v3...
for i, filename in enumerate(files):
file_path = os.path.join(workflow_directory, filename)
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
signature = data.get('signature')
author_id = data.get('author_id')
workflow_data = data.get('workflow_data')
# === AUDIT CHECK #1: PROVE AUTHORSHIP ===
# Re-create the data block that was signed
unsigned_data_block = {"workflow_data": workflow_data}
message_to_verify = json.dumps(unsigned_data_block, sort_keys=True, separators=(',', ':'))
encoded_message = encode_defunct(text=message_to_verify)
# Recover the signer's address from their signature
recovered_address = w3.eth.account.recover_message(encoded_message, signature=signature)
if recovered_address.lower() != author_id.lower():
raise ValueError(f"Invalid signature in {filename}. Author mismatch!")
# === AUDIT CHECK #2: PROVE INTEGRITY ===
if i == 0: # This is v1, it should have no parent
if data.get('previous_hash') is not None:
raise ValueError(f"Chain broken at {filename}: First file has a previous_hash.")
else: # This is v2 or later
if data.get('previous_hash') != previous_file_hash:
raise ValueError(f"Chain broken at {filename}! Hash mismatch.")
# 3. The chain is valid so far. Store this file's hash
# to check it against the *next* file.
previous_file_hash = calculate_hash(file_path)
return True, "Chain verified."
This is what "auditable" actually means.
- Authorship Proof (Check #1): This proves who made the change. Because it's signed with their private key, it's non-repudiable. You can't say, "It wasn't me, my account was hacked." Yes, it was, and they used your key.
-
Integrity Proof (Check #2): This proves the history. If an attacker deletes
v2_...jsonor changes one byte inside it, its hash will change. When the auditor checksv3_...json, itsprevious_hash(which stored the original hash of v2) will no longer match. The chain is instantly detected as broken.
π‘οΈ Pillar 3: The "Steel Fortress" That Protects the Auditor
Okay, final question for the big-brain hackers.
"If I can't modify the workflow files without breaking the chain... what if I just modify the auditor bot itself? What if I just edit flowchain_verifier.py to return True?"
Flowork thought of that. It's a service called the "Integrity Checker," nicknamed "Benteng Baja" (Steel Fortress).
Code Deep Dive 7: The App Audits Itself
Before Flowork even starts, this service runs and checks the integrity of all of its own core files.
# A look at C:\FLOWORK\flowork-core\flowork_kernel\services\integrity_checker_service\integrity_checker_service.py
import os
import json
import hashlib
class IntegrityCheckerService(BaseService):
def __init__(self, kernel, service_id: str):
super().__init__(kernel, service_id)
# The true root is C:\FLOWORK\
self.true_root_path = os.path.abspath(os.path.join(self.kernel.project_root_path, ".."))
# This is the master list of "correct" file hashes
self.core_manifest_path = os.path.join(self.true_root_path, "core_integrity.json")
def _calculate_sha256(self, file_path):
#... (calculates SHA-256 hash)...
pass
def verify_core_files(self):
self.kernel.write_to_log("Benteng Baja: Verifying core file integrity...", "INFO")
with open(self.core_manifest_path, "r", encoding="utf-8") as f:
full_integrity_manifest = json.load(f)
# Loop through EVERY file the app needs to run...
for rel_path, expected_hash in full_integrity_manifest.items():
# This includes "flowchain_verifier.py", "preset_manager_service.py", etc.
full_path = os.path.join(self.true_root_path, rel_path.replace("/", os.sep))
# Calculate the file's hash right now
current_hash = self._calculate_sha256(full_path)
if current_hash is None:
raise RuntimeError(f"Integrity Check Failed: Core file '{rel_path}' is missing!")
# Compare it to the "correct" hash
if current_hash != expected_hash:
# If they don't match, shut down the entire application.
raise RuntimeError(f"Integrity Check Failed: Core file '{rel_path}' has been modified!")
self.kernel.write_to_log(
f"Benteng Baja: All {len(full_integrity_manifest)} files passed.", "SUCCESS"
)
This is the final checkmate.
- An attacker can't modify the workflow history (Pillar 2) without the Auditor Bot catching it.
- An attacker can't modify the Auditor Bot itself without the "Steel Fortress" (Pillar 3) catching that and refusing to even start the app.
π€ The Verdict: Mic Drop.
What we've just walked through isn't a "feature list." It's a complete, end-to-end security philosophy built on cryptographic proof, not "trust me" promises.
Let's put it all together.
| Feature | Traditional Automation (Zapier, n8n) | Flowork ("Flowchain" Model) |
|---|---|---|
| Identity System | Email + Password. Vulnerable to DB breach. | Private Key Signature. DB breach is a non-event. |
| Data Privacy | Cloud-Only. All data processed on their servers. | Secure Hybrid. Data never leaves your on-prem engine. |
| Audit Log Storage | A mutable log in a cloud database. | An append-only file chain on your local disk. |
| Proof of Change | A simple string: "Admin updated this." | A cryptographic signature from the user's private key. |
| Proof of History | None. You must trust the log wasn't altered. |
Mathematical. The previous_hash chain proves history is intact. |
| Non-Repudiation | No. An admin can claim, "My account was hacked." | Yes. A user cannot deny a change signed by their key. |
| Third-Party Audit | Impossible. Requires giving an auditor full admin. | Trivial. Zip the workflow folder and email it. The auditor can verify it offline. |
This is what it means to move from being a simple "connector" of apps to an "architect" of provable systems.
The "easiest way" to build auditable, cryptographically-secure workflows isn't a magic button. It's choosing a platform that was architected from line one to make "proof" a core part of the system, not an enterprise afterthought. The "easy" part is that Flowork handles all this complexity for you.
All you have to do is build.
https://github.com/flowork-dev/Visual-AI-Workflow-Automation-Platform
Top comments (0)