We take security vulnerabilities seriously. If you discover a security issue, please report it responsibly.
DO NOT open a public GitHub issue for security vulnerabilities.
Instead, please email: [email protected] (or open a private security advisory on GitHub)
Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
| Action | Timeframe |
|---|---|
| Initial response | 48 hours |
| Vulnerability confirmation | 7 days |
| Fix development | 30 days (critical: 7 days) |
| Public disclosure | After fix is deployed |
| Component | Location |
|---|---|
| Smart Contracts | packages/contracts/src/ |
| Core Library | packages/core/src/ |
| SDK | packages/sdk/src/ |
| CLI | packages/cli/src/ |
| GitHub Action | packages/action/src/ |
| GitLab CI Component | packages/gitlab-ci/src/ |
| Bitbucket Pipe | packages/bitbucket-pipe/src/ |
- Third-party dependencies (report to upstream)
- Demo application (
packages/demo/) - Documentation websites
This security self-review was conducted to identify potential vulnerabilities and document the security posture of PoHI before public release.
| Area | Status | Notes |
|---|---|---|
| Threat Model | ✅ Documented | 10 threat categories analyzed |
| Core Library | ✅ Reviewed | Deterministic hashing, type validation |
| Smart Contract | ✅ Reviewed | No external calls, overflow protection |
| GitHub Action | ✅ Reviewed | Trusted context, URL trust documented |
| GitLab CI | ✅ Reviewed | Token scoping, URL trust documented |
| Bitbucket Pipe | ✅ Reviewed | Docker isolation, token scoping |
| ZK Proof Boundaries | ✅ Documented | Edge cases identified |
| Cryptographic Design | ✅ Documented | SHA-256/Keccak-256 usage explained |
| Dependency Audit | ✅ Reviewed | Key dependencies documented |
| Item | Priority | Status |
|---|---|---|
| External smart contract audit | High | Planned |
| Formal verification of ZK circuits | Medium | Not planned (uses World ID's verified circuits) |
| Penetration testing | Medium | Planned post-launch |
| Bug bounty program | Medium | Planned |
| Severity | Count | Status |
|---|---|---|
| Critical | 0 | - |
| High | 0 | - |
| Medium | 5 | All documented with mitigations |
| Low | 2 | All documented |
| Informational | 4 | By design |
- World ID Orb: Provides strong practical Sybil resistance through biometric-based uniqueness checks (does not claim formal biometric infallibility)
- Cryptographic Primitives: SHA-256, Keccak-256, ZK proofs are computationally secure
- Blockchain: World Chain provides finality and censorship resistance
- Time: Block timestamps are reasonably accurate (within minutes)
- GitHub Context:
github.context.shain Actions is trustworthy
- A unique human (as verified by World ID) approved a specific signal (commit SHA)
- The approval is timestamped and tamper-evident
- The same human cannot approve the same commit twice (nullifier uniqueness)
- That the human understood what they approved (semantic gap)
- That the human intended to approve that specific action (UI/social engineering)
- That the human is authorized by the organization (requires Authority Layer)
- That the code being approved is safe or correct
| Aspect | Details |
|---|---|
| Threat | Attacker creates multiple identities to approve malicious commits |
| Mitigation | World ID nullifier hash ensures one identity per human per scope |
| Assurance Level | High (Orb), Medium (Device) |
| Residual Risk | World ID verification accuracy; colluding individuals |
| Aspect | Details |
|---|---|
| Threat | Reusing a valid proof from one commit for another |
| Mitigation | Signal = SHA256(repository:commit_sha) binds proof to specific commit |
| Assurance Level | High |
| Residual Risk | None identified |
| Aspect | Details |
|---|---|
| Threat | Attacker observes pending approval and submits before legitimate user |
| Mitigation | On-chain nullifier check; ZK proof required (attacker lacks proof) |
| Assurance Level | High |
| Residual Risk | Theoretical MEV concerns (mitigated by World Chain design) |
| Aspect | Details |
|---|---|
| Threat | Attacker claims to be an authorized approver |
| Mitigation | World ID ZK proof cryptographically proves humanity |
| Assurance Level | High |
| Residual Risk | Compromised World ID credentials (user responsibility) |
| Aspect | Details |
|---|---|
| Threat | Modifying attestation data after creation |
| Mitigation | SHA-256 hash covers all fields; on-chain immutability |
| Assurance Level | High |
| Residual Risk | None identified |
| Aspect | Details |
|---|---|
| Threat | Malicious workflow fakes approval status |
| Mitigation | github.context.sha from trusted runner environment |
| Attack Vector | Compromised CI runner, malicious Action dependency |
| Assurance Level | Medium |
| Residual Risk | Supply chain attack on GitHub Actions itself |
| Aspect | Details |
|---|---|
| Threat | approval-url points to attacker-controlled server returning fake "approved" |
| Mitigation | Server must return valid World ID proof (verified by World ID API) |
| Recommendation | Users should only use trusted approval URLs |
| Assurance Level | Medium (depends on server trust) |
| Aspect | Details |
|---|---|
| Threat | Human approves without understanding the commit content |
| Status | Explicitly out of scope for cryptographic protocol |
| Mitigation | UI best practices (show commit details clearly) |
| Recommendation | Display commit diff, author, and description before approval |
| Aspect | Details |
|---|---|
| Threat | Human is tricked or coerced into approving |
| Status | Out of scope for cryptographic protocol |
| Mitigation | Organizational security policies, education |
| Aspect | Details |
|---|---|
| Threat | Authorized human intentionally approves malicious code |
| Status | Out of scope (human verified correctly, intent is malicious) |
| Mitigation | Multi-signature requirements, code review policies |
Attack: Attacker steals victim's phone with World App and approves malicious commit.
Attacker → Steals phone → Opens World App → Scans approval QR → Malicious code deployed
Analysis:
- PoHI correctly records that "a human approved" the commit
- The human verification is technically valid
- This is a credential theft issue, not a protocol flaw
Mitigations:
- World App biometric lock (FaceID/fingerprint)
- Device-level security (passcode)
- Organization policy: require multiple approvers for production
Residual Risk: Accepted (same as any credential-based system)
Attack: Compromised CI runner reports fake GITHUB_SHA / CI_COMMIT_SHA.
Attacker → Compromises self-hosted runner → Modifies environment → Reports wrong SHA
Analysis:
- If runner is compromised, attacker controls the environment
- PoHI would record approval for the wrong commit
- This is a supply chain attack on CI infrastructure
Mitigations:
- Use GitHub/GitLab hosted runners (trusted environment)
- Runner hardening and monitoring
- Compare attestation SHA with actual deployed code
Residual Risk: Medium (depends on runner security)
Attack: Attacker intercepts traffic to approval server and injects fake "approved" response.
CI Job → HTTPS request → Attacker MITM → Fake "approved" response → CI proceeds
Analysis:
- Requires breaking TLS (unlikely) or DNS hijacking
- Even if successful, no valid World ID proof exists
- On-chain verification would fail
Mitigations:
- HTTPS enforced for approval URLs
- Certificate pinning (future enhancement)
- On-chain attestation verification
Residual Risk: Low (requires significant infrastructure compromise)
Attack: Use approval from Repo A to authorize deploy in Repo B.
Attacker → Gets approval for repo-a:abc123 → Tries to use for repo-b:abc123
Analysis:
- Signal = SHA256("repo-a:abc123") ≠ SHA256("repo-b:abc123")
- ZK proof is bound to specific signal
- Verification would fail
Result: ✅ Attack prevented by protocol design
Attack: Two deployments try to use same approval simultaneously.
Deploy Job 1 → Checks approval → ✓
Deploy Job 2 → Checks approval → ✓ (same attestation)
Both deploy simultaneously
Analysis:
- This is not a security issue (same approval used twice is fine)
- The approval is for a specific commit, deploying it twice is valid
- If this is undesired, use on-chain nullifier check
Mitigations:
- On-chain recording marks attestation as "used"
- CI job should mark approval as consumed after use
Residual Risk: Low (depends on use case)
Attack: Same human tries to approve same commit twice (double voting).
Human A → Approves commit X → Recorded with nullifier N
Human A → Tries to approve commit X again → Same nullifier N
Analysis:
- Contract checks:
nullifierUsed[commitKey][nullifierHash] - Second attempt would fail:
AlreadyApproved()
Result: ✅ Attack prevented by nullifier uniqueness
| Finding | Severity | Status |
|---|---|---|
| Canonical JSON serialization ensures deterministic hashing | ✅ Good | N/A |
| SHA-256 used for attestation hash | ✅ Good | N/A |
Hash integrity verification in validateAttestation() |
✅ Good | N/A |
parseAttestation() uses type assertion without runtime validation |
Documented |
Recommendation: Callers of parseAttestation() should call validateAttestation() on the result.
| Finding | Severity | Status |
|---|---|---|
Uses github.context.sha (trusted source) |
✅ Good | N/A |
| Status API response not cryptographically verified | Documented | |
Depends on approval-url being trustworthy |
Documented |
Recommendation:
- Only configure trusted
approval-urlvalues - Future: Add on-chain verification of attestation in Action
| Finding | Severity | Status |
|---|---|---|
| Solidity 0.8.24 with built-in overflow protection | ✅ Good | N/A |
| No external calls (no reentrancy risk) | ✅ Good | N/A |
| Input validation on all parameters | ✅ Good | N/A |
| Duplicate approval check prevents same human approving same commit | ✅ Good | N/A |
| Unbounded loop in duplicate check could cause DoS | Documented | |
Permissionless recording (anyone can call recordAttestation) |
ℹ️ Info | By Design |
| Admin can revoke any attestation | ℹ️ Info | By Design |
| World ID proof not verified on-chain | ℹ️ Info | By Design (off-chain) |
Note on Permissionless Design: The contract intentionally allows anyone to record attestations. The security comes from:
- World ID proof verification (off-chain)
- Attestation hash uniqueness
- Nullifier-based duplicate prevention
| Finding | Severity | Status |
|---|---|---|
Uses CI_COMMIT_SHA from GitLab CI environment (trusted) |
✅ Good | N/A |
Uses CI_JOB_TOKEN or custom GITLAB_TOKEN for API auth |
✅ Good | N/A |
| Status API response not cryptographically verified | Documented | |
Depends on POHI_APPROVAL_URL being trustworthy |
Documented | |
| MR notes expose approval URL (information disclosure) | ℹ️ Info | By Design |
Recommendations:
- Only configure trusted
POHI_APPROVAL_URLvalues - Use
CI_JOB_TOKENwhere possible (limited scope) - For custom tokens, use minimal required permissions
| Finding | Severity | Status |
|---|---|---|
Uses BITBUCKET_COMMIT from Pipelines environment (trusted) |
✅ Good | N/A |
Requires BITBUCKET_TOKEN with repository write access |
Documented | |
| Status API response not cryptographically verified | Documented | |
Depends on POHI_APPROVAL_URL being trustworthy |
Documented | |
| Runs in Docker container (isolated environment) | ✅ Good | N/A |
Recommendations:
- Create App Password with minimal scopes (
repository:write,pullrequest:write) - Only configure trusted
POHI_APPROVAL_URLvalues - Store
BITBUCKET_TOKENas secured repository variable
| Parameter | Validation |
|---|---|
proof |
Valid ZK-SNARK proof |
merkle_root |
Must be in valid root set |
nullifier_hash |
Derived from identity; unique per action scope |
signal |
Must match expected SHA256(repository:commit) |
external_nullifier |
App-specific action identifier |
- Expired Merkle Root: World ID has a root expiration window. Old proofs may fail.
- Action Scope Change: If
worldIdActionchanges, existing nullifiers don't apply. - Signal Mismatch: Commit SHA must exactly match; short SHA not accepted.
| Contract | Auditor | Status | Report |
|---|---|---|---|
| PoHIRegistry.sol | - | Pending | - |
- Access Control: Owner-based administration for admin management
- Reentrancy Protection: No external calls in state-changing functions
- Integer Overflow: Solidity 0.8.x built-in checks
- Nullifier Uniqueness: Mapping prevents duplicate approvals per commit
- Custom Errors: Gas-efficient error handling
- Non-upgradeable: Contracts cannot be upgraded (new versions = new addresses)
- Permissionless Recording: Security relies on off-chain proof verification
- Admin Revocation: Admins can revoke any attestation (for emergency response)
| Purpose | Algorithm | Implementation |
|---|---|---|
| Attestation Hash (off-chain) | SHA-256 | Node.js crypto |
| Attestation Hash (on-chain) | Keccak-256 | Solidity native |
| Signal Hash | SHA-256 | Protocol standard |
| Commit Key | Keccak-256 | On-chain indexing |
- SHA-256: Protocol standard, widely supported, interoperable
- Keccak-256: EVM native, gas efficient for on-chain operations
Both hashes are computed for each attestation to support both off-chain verification and on-chain storage.
| Package | Purpose | Security Posture |
|---|---|---|
viem |
EVM interaction | Widely audited, maintained by Paradigm |
@worldcoin/idkit |
World ID integration | Maintained by Worldcoin Foundation |
@actions/core |
GitHub Actions SDK | Maintained by GitHub |
@actions/github |
GitHub API | Maintained by GitHub |
- Security patches: Immediate update
- Minor versions: Weekly review
- Major versions: Compatibility testing required
- Trust Your Approval Server: Only use
approval-urlvalues you control or trust - Require Orb Verification: Use
verification-level: orbfor high-security workflows - Complement with Code Review: PoHI proves human approval, not code correctness
- Monitor Attestations: Watch for unexpected approvals on your repositories
- Multi-Approver Policies: Consider requiring multiple human approvals for critical changes
- Verify Commit Details: Always check the commit SHA and repository before approving
- Secure Your World ID: Protect your World ID credentials
- Report Suspicious Requests: If asked to approve something unexpected, investigate first
| Component | Method | Status | Date |
|---|---|---|---|
| World ID Integration | Manual testing with Orb verification | ✅ Verified | 2026-01 |
| Attestation Hashing | Unit tests (deterministic output) | ✅ Verified | 2026-01 |
| Nullifier Uniqueness | Unit tests + manual testing | ✅ Verified | 2026-01 |
| Signal Binding | Unit tests | ✅ Verified | 2026-01 |
| On-chain Recording | Testnet deployment | ✅ Verified | 2026-01 |
| Package | Coverage | Critical Paths |
|---|---|---|
pohi-core |
96% | ✅ All covered |
pohi-sdk |
100% | ✅ All covered |
pohi-evm |
100% | ✅ All covered |
| Smart Contracts | Foundry tests | ✅ All covered |
| Provider | Status | Notes |
|---|---|---|
| World ID (Orb) | ✅ Tested | Primary provider, production-ready |
| World ID (Device) | ✅ Tested | Lower assurance level |
| Gitcoin Passport | ✅ Tested | API v2 verified (2025-12), Score: 54.33, HIGH_TRUST level |
| BrightID | 🔧 Implemented | API integration complete |
| Civic | 🔧 Implemented | Gateway integration complete |
| Proof of Humanity | 🔧 Implemented | Subgraph integration complete |
| Platform | Status | Notes |
|---|---|---|
| GitHub Actions | ✅ Tested | Production-ready |
| GitLab CI | 🔧 Implemented | Ready for testing |
| Bitbucket Pipelines | 🔧 Implemented | Ready for testing |
Coming soon. We plan to launch a bug bounty program after initial security audit.
| Severity | Reward |
|---|---|
| Critical | Up to $10,000 |
| High | Up to $5,000 |
| Medium | Up to $1,000 |
| Low | Up to $200 |
Before submitting a PR:
- No secrets or private keys in code
- Input validation for all user inputs
- No unsafe type assertions without validation
- Error messages don't leak sensitive info
- Dependencies are up to date
- Tests cover security-critical paths
- Consider replay, Sybil, and tampering attacks
| Version | Date | Changes |
|---|---|---|
| 1.2 | 2026-01 | Added GitLab CI/Bitbucket Pipe, self-review summary, attack scenarios |
| 1.1 | 2025-12 | Added detailed threat analysis, component review |
| 1.0 | 2025-12 | Initial security policy |
- Security issues: [email protected]
- General questions: Open a GitHub Discussion