Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
terrapod-PoC — PoC — missing authorization on the platform-wide GPG trust-anchor store in Terrapod (GHSA-6qrc-597p-mrp9, CVE-2026-87006, CVSS 6.5). | Kitploit
Tools/GitHubGitHub/squeeze440/terrapod-poc
Authentication & AuthorizationVulnerability AnalysisExploitationWeb SecurityPenetration TestingSupply Chain SecurityPapers & Research
GitHubsqueeze440/terrapod-poc

terrapod-PoC

PoC — missing authorization on the platform-wide GPG trust-anchor store in Terrapod (GHSA-6qrc-597p-mrp9, CVE-2026-87006, CVSS 6.5).

View Repository
7 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Terrapod: security advisory

CVE status: requested, pending assignment. This finding is published as GHSA-6qrc-597p-mrp9. On CVE assignment this repository is renamed CVE-YYYY-NNNNN-terrapod-PoC and this banner is replaced with the CVE link.

ResearcherDostxodjayev Abdullox (@squeeze440)
AdvisoryGHSA-6qrc-597p-mrp9
CVSS 3.16.5 (Medium)
WeaknessCWE-862, CWE-284

Summary

Missing Authorization in the GPG-key management API in Terrapod (main @ b36d953, post-v1.3.1) allows any authenticated principal — including a user with only the built-in everyone role and zero capability grants, or a scoped runner token — to create and delete entries in the platform-wide GPG trust-anchor store used to verify signatures on every provider published to the private registry, via POST /api/terrapod/v1/gpg-keys and DELETE /api/terrapod/v1/gpg-keys/{key_id}.

Product

Terrapod (mattrobinsonsre/terrapod) — self-hosted Terraform Enterprise / HCP Terraform replacement.

Tested Version

Commit b36d9535dedc31d85a02093d78f04da748492a2d (main), 10 commits ahead of the closest release tag v1.3.1. (v1.3.2 exists as a tag but sits on the release/v1.3 branch and is not yet merged to main; the bug is present on both.)

Estimated CVSS v3.1

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N — 6.4 (Medium)

PR:L (not N): every request still needs a valid credential — a session, an API token, or even a run-scoped runtok: runner token — just not a privileged one. I:H/C:N/A:N: the bug lets an unprivileged caller corrupt the integrity of the registry's shared signing-key trust store (delete legitimate keys, insert their own), but doesn't itself disclose secret key material (private keys are never serialized in any response) or take the service offline.

Details

services/terrapod/api/routers/gpg_keys.py implements CRUD for GPGKey rows — the ASCII-armored public keys Terrapod trusts to verify the detached SHA256SUMS.sig on every provider version published to the private registry (services/terrapod/services/registry_provider_service.py:191-201, _verify_and_store_shasums_signature). Every route in the router is gated only by Depends(get_current_user) — i.e. "some authenticated principal" — with no role or capability check at all:

  • create_gpg_key_endpoint — services/terrapod/api/routers/gpg_keys.py:104-130
  • list_gpg_keys_endpoint — gpg_keys.py:133-146
  • show_gpg_key_endpoint — gpg_keys.py:149-166
  • revoke_gpg_key_endpoint — gpg_keys.py:168-193
  • delete_gpg_key_endpoint — gpg_keys.py:195-213

The underlying service functions (services/terrapod/services/gpg_key_service.py:144 create_gpg_key, :316 delete_gpg_key) take no caller/ownership argument at all — GPGKey has no namespace or owner column (_gpg_key_to_jsonapi hardcodes "namespace": "default"; the namespace field accepted on create is parsed by the request model but never passed through). The key set is one global, shared list for the whole platform.

Contrast this with every other admin-owned platform resource in the same codebase — tokens.py, roles.py, vcs_connections.py, role_assignments.py — which all gate mutating routes behind require_admin or an explicit bound_to == user.email or is_admin ownership check. gpg_keys.py is the one router in api/routers/ that manages a platform-wide security-critical resource with none of that.

Impact chain: get_gpg_key_by_key_id() (registry_provider_service.py:195) does a global, unscoped lookup — any key ever registered, by anyone, is a valid trust anchor for any provider publish (this is by design for self-service publisher key registration — the error message even says add it via /api/terrapod/v1/gpg-keys first). Because registration has no auth check, and deletion has no auth check either:

  1. Any authenticated user (or a leaked/observed runner token, which require_non_runner exists specifically to keep off "resource creation and management endpoints" per its own docstring in services/terrapod/api/dependencies.py:387-400, but which this router never uses) can delete any other tenant's registered signing key, breaking signature verification for every provider version that tenant already published — a cross-tenant integrity attack with zero relationship to the target namespace required.
  2. The same caller can register a new trusted key, expanding the platform's shared trust anchor set with no gate beyond having any credential.

The project's own test suite documents this gap without questioning it — services/tests/api/test_gpg_keys.py builds its create/delete/revoke happy-path tests with AuthenticatedUser(roles=["everyone"], ...) (see _user(), line 23, used throughout TestCreate/TestDelete/TestRevoke) and asserts 201/204 for that unprivileged user — i.e. the tests assert the vulnerable behavior as correct, they just were never written to ask "should everyone be allowed to do this?"

Proof of Concept

Dynamically verified against the real application (real Postgres + Redis via the project's own docker-compose.test.yml integration-test harness — no mocks, no unrelated modifications to app code):

  1. Wrote services/tests/integration/test_gpg_key_missing_authz_poc.py:
    • test_everyone_role_user_can_delete_admins_signing_key — an admin registers a real RSA-2048 PGP public key via POST /api/terrapod/v1/gpg-keys (201, confirmed present in Postgres), then a second user authenticated with roles=["everyone"] only (no admin, no capability grants) sends DELETE /api/terrapod/v1/gpg-keys/{key_id} and it succeeds (204); the row is confirmed gone from Postgres afterward.
    • test_everyone_role_user_can_register_new_trusted_key — the same unprivileged user registers a brand-new key via POST /api/terrapod/v1/gpg-keys (201).
  2. Built the test image and ran it against real infra:
    root@kitploit:~
    docker build -f docker/Dockerfile.test -t terrapod-test:local .
    docker compose -f docker-compose.test.yml run --rm test \
      pytest tests/integration/test_gpg_key_missing_authz_poc.py -v -m integration
    
    Result: 2 passed — both assertions (the unauthorized delete succeeding, and the row actually disappearing from the real database) held. Screenshot: .

Impact

Any authenticated user of a Terrapod instance — regardless of role, workspace access, or registry permissions, down to the built-in unprivileged everyone role or a single run's scoped runner token — can tamper with the platform's shared GPG trust-anchor store: delete another team's registered signing key (breaking terraform init signature verification for every provider version they already published, platform-wide) and/or add new keys to the trusted set. This undermines the "GPG-signed private module + provider registry" supply-chain guarantee the project advertises as a headline feature, without requiring any workspace or registry privilege on the attacker's part.

Weaknesses

  • CWE-862: Missing Authorization
  • CWE-284: Improper Access Control

Remediation

Add a capability/role gate to the mutating routes in services/terrapod/api/routers/gpg_keys.py, matching the pattern already used by tokens.py/roles.py/vcs_connections.py — e.g. Depends(require_admin) on create_gpg_key_endpoint, delete_gpg_key_endpoint, and revoke_gpg_key_endpoint (revoke is separately protected by requiring a valid self-revocation certificate, but should still not be reachable by an arbitrary caller for other tenants' keys). list/show are lower risk (the armor blocks are public keys by design) but should probably also require at least require_non_runner for consistency. Consider also introducing a namespace/owner column on GPGKey if self-service per-namespace publisher keys are the intended model, so a namespace owner can manage only their own key(s) rather than the single global list.

Credit: Dostxodjayev Abdullox

Reporting Channel: Per SECURITY.md, do not open a public issue. Use GitHub's private vulnerability reporting: go to https://github.com/mattrobinsonsre/terrapod/security/advisories/new, click "Report a vulnerability", and fill in the description, steps to reproduce, and affected versions. (If PVR is unavailable, the policy says to email the maintainer directly.)

Download Tool
evidence/gpg_key_authz_poc_run3.png