Inspiration
Modern CI/CD pipelines increasingly rely on automated security scanners to catch secrets, vulnerable code, and other risks before software is merged or deployed.
However, the scanner results themselves can contain sensitive security information. A detailed report may reveal where credentials were exposed, which source-code paths are vulnerable, or other information about an organization's internal security posture.
That led to the idea behind CJGate:
Can a development team prove that its repository satisfies a security policy without revealing the underlying security findings?
CJGate explores that idea by combining real DevSecOps security scanners with Midnight private witnesses and zero-knowledge proofs.
Instead of publishing detailed scanner results, CJGate keeps the security signals private and proves whether the repository satisfies the required security policy.
What it does
CJGate is a privacy-preserving DevSecOps security gate.
It currently integrates two real security scanners:
- Gitleaks for secret detection
- Semgrep for static application security testing
The scanner outputs are processed locally and normalized into two private security signals:
secretsFound
sastHighFindings
These values are supplied to a Midnight Compact contract as private witness data.
The current security policy is:
secretsFound == 0
AND
sastHighFindings == 0
The numeric values are not written to the public ledger and are not printed in CI or proof-flow logs.
If the repository satisfies the policy, the Compact circuit succeeds and the public state can become:
policyPassed = true
If either private security signal violates the policy, the Compact assertion rejects the transition before a valid proof or blockchain transaction is submitted.
How we built it
CJGate is composed of four main layers.
1. Security scanning
CJGate runs Gitleaks and Semgrep directly against a repository or target directory.
Repository
|
+--> Gitleaks --> private secretsFound
|
+--> Semgrep --> private sastHighFindings
The raw findings are processed locally.
CJGate avoids printing secret contents, vulnerable source snippets, scanner finding details, or private finding counts.
2. Midnight Compact security policy
The private scanner signals are passed into a Compact contract.
The contract evaluates:
secretsFound == 0
AND
sastHighFindings == 0
Only the policy result is public.
This creates a separation between:
Private security evidence
and
Public security-policy verification
3. GitHub Actions CI gate
CJGate includes a GitHub Actions workflow that runs on pushes to main and pull requests targeting main.
The workflow installs and runs:
- Node.js 22
- Gitleaks
- Semgrep
- Compact compiler 0.31.1
It then verifies:
- clean fixture -> PASS
- synthetic secret fixture -> BLOCK
- synthetic SAST fixture -> BLOCK
- actual repository -> PASS or BLOCK
For intentionally vulnerable fixtures, BLOCK is the expected test result.
For the actual repository scan, a BLOCK result returns a non-zero exit code and fails the GitHub Actions job.
We verified this behavior with a test pull request containing intentionally vulnerable code. The normal repository produced a successful CI run, while the vulnerable pull request produced a failed CJGate security check.
The CI path does not require a wallet, Midnight node, or blockchain transaction. It evaluates the Compact policy locally so it can remain practical for regular CI/CD use.
4. Real Midnight zero-knowledge proof flow
CJGate also provides a separate live proof path.
For a clean repository:
Gitleaks + Semgrep
|
v
Private security signals
|
v
Midnight Compact contract
|
v
Midnight Proof Server
|
v
Real zero-knowledge proof
|
v
Midnight transaction
We first validated this flow against a local Midnight devnet.
We then deployed the current CJGate contract to Midnight Preprod and executed the same flow against the public Preprod network.
Midnight Preprod verification
CJGate is deployed on Midnight Preprod.
Contract address
c0fb1764ead1c0aa4196f1dfa6ae6657b6d4cb4a8fcab60fff94dbb317875066
Verified PASS transaction
00011d8c27cb17b22ed12f129ac275f7becea0f3d7fe629c83111e6ca9e2ca2343
Block height
2308502
During the successful Preprod run:
policyPassed: false -> true
proofProvider.proveTx calls: 1
walletProvider.submitTx calls: 1
POST /prove requests: 2
POST /check requests: 1
The Midnight proof server generated and verified the real zero-knowledge proof, and the resulting transaction was submitted to Preprod.
We also tested both failure paths on Preprod.
Secret violation
Gitleaks detects a violation
|
v
Compact assertion fails
|
+--> proveTx calls: 0
+--> submitTx calls: 0
+--> POST /prove requests: 0
+--> policyPassed unchanged
SAST violation
Semgrep detects a violation
|
v
Compact assertion fails
|
+--> proveTx calls: 0
+--> submitTx calls: 0
+--> POST /prove requests: 0
+--> policyPassed unchanged
This demonstrates an important property of CJGate: a repository that violates the private policy does not generate a valid proof, does not submit a transaction, and does not modify the public ledger state.
Privacy model
CJGate is designed so that the following information remains private:
- secret contents
- Gitleaks findings
- Semgrep findings
- vulnerable source snippets
- matched strings
- scanner finding counts
secretsFoundsastHighFindings- wallet mnemonics
- wallet private keys
The public blockchain only needs to observe the resulting policy state.
This allows security compliance to be verified without publishing the underlying security evidence.
Challenges we ran into
Separating CI evaluation from a real ZK proof
One of the first challenges was understanding the difference between evaluating a compiled Compact policy locally and generating a real Midnight zero-knowledge proof.
A local Compact evaluation is useful for CI because it is fast and does not require a wallet or blockchain transaction.
However, proving the complete privacy-preserving flow required connecting the deployed contract to the Midnight Proof Server, generating the actual proof, submitting a transaction, and reading the resulting public ledger state.
CJGate therefore keeps these as two separate execution paths:
CI path
Scanner -> private signals -> local Compact evaluation -> PASS/BLOCK
Live path
Scanner -> private witnesses -> Compact contract -> ZK proof -> Midnight transaction
Preprod wallet synchronization and DUST
The most time-consuming challenge was deploying the project to Midnight Preprod.
The dedicated CJGate wallet had to synchronize multiple wallet states, and the DUST wallet synchronization required significantly more time than the other wallet components.
After receiving test NIGHT from the faucet, the NIGHT UTXO also had to be registered for DUST generation before the contract could be deployed.
During that registration process, the Preprod RPC connection repeatedly closed while a transaction was being submitted.
We added bounded retry and state re-checking logic so CJGate could distinguish transient network failures from permanent transaction errors and avoid blindly submitting duplicate registration transactions.
On a later retry, DUST registration finalized successfully and the CJGate contract was deployed to Preprod.
Privacy-safe security automation
Another challenge was making sure that the security automation itself did not accidentally leak the information CJGate was designed to protect.
Scanner reports are processed locally and temporary reports are removed after use.
The logs expose only high-level information such as:
Gitleaks scan completed
Semgrep scan completed
Security policy passed
Security policy blocked
The underlying findings and private numeric security signals are not printed.
What we learned
CJGate helped us understand how zero-knowledge proofs can be applied to software security workflows rather than only financial applications.
In particular, we learned how to:
- write and compile Midnight Compact contracts
- separate private witness data from public ledger state
- connect real DevSecOps scanner results to private witnesses
- generate real zero-knowledge proofs with the Midnight Proof Server
- submit and verify Midnight transactions
- deploy and interact with a contract on Midnight Preprod
- integrate privacy-aware policy evaluation into GitHub Actions
- handle Preprod wallet synchronization and DUST generation
- design logs and temporary scanner artifacts so sensitive findings remain private
The project also demonstrated that privacy and CI/CD automation do not need to be mutually exclusive.
Security policies can still automatically PASS or BLOCK software while the evidence behind those decisions remains private.
What's next
CJGate currently proves a policy based on secret detection and SAST findings.
The same architecture can be extended into a broader privacy-preserving DevSecOps policy engine.
Additional private security signals could come from tools such as:
- OSV-Scanner for vulnerable dependencies
- Trivy for container and infrastructure security
- IaC security scanners
- software supply-chain checks
A future policy could evaluate requirements such as:
secretsFound == 0
criticalDependencies == 0
highSeveritySastFindings == 0
criticalMisconfigurations == 0
without revealing the underlying findings.
The longer-term goal is to make CJGate a reusable privacy layer for software security compliance, allowing organizations to prove that software satisfies security requirements without publishing sensitive security evidence.
Built With
- ci/cd
- compact
- devsecops
- docker
- github
- github-actions
- gitleaks
- midnight
- node.js
- typescript
- zero-knowledge
Log in or sign up for Devpost to join the conversation.