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
  • secretsFound
  • sastHighFindings
  • 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

Share this project:

Updates

Submission history