The form at docs/deploy/index.html and the workflow at
.github/workflows/deploy-to-tenant.yml constitute a trust boundary. Customer
tenant credentials enter via the form, get RSA-OAEP + AES-GCM encrypted in the
browser against the embedded public key, and are decrypted only inside a
workflow run using PAYLOAD_PRIVATE_KEY.
The realistic compromise scenarios for this single-maintainer repo:
- Pushed code on
mainis altered (compromised GitHub account, compromised laptop, or sophisticated supply-chain attack against tooling). - The
PAYLOAD_PRIVATE_KEYsecret is exfiltrated via a modified workflow.
Mitigations layered against these:
- Hash pinning via
Integrity Checkworkflow. Any change todocs/deploy/index.htmlordeploy-to-tenant.ymlfails CI unless the corresponding repository variable (DEPLOY_FORM_SHA256,DEPLOY_WORKFLOW_SHA256) is updated through the GitHub Settings UI. The Settings UI is a separate access path fromgit push. For a solo maintainer this is detection rather than prevention: the check fails after the push, raising a visible red status and a failure notification, but the push has already landed onmain. The maintainer must push a fix or revert. - Signed commits required on
main. Commits must carry a valid SSH or GPG signature. Hardware-backed signing keys (Touch ID or YubiKey) cannot be exercised by remote attackers or laptop malware without physical presence. - Browser-side payload encryption. Even with full repo read access, an
attacker cannot decrypt past dispatched payloads. Decryption requires
PAYLOAD_PRIVATE_KEY, which is only readable inside a workflow run. - Replay window. Encrypted payloads carry a nonce and ISO8601 timestamp; the workflow rejects payloads older than 5 minutes.
Rotate quarterly, or immediately on suspected compromise.
- Generate new keypair outside the repo:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out /tmp/deploy_private.pem openssl rsa -in /tmp/deploy_private.pem -pubout -out /tmp/deploy_public.pem
- Update the
PUBLIC_KEY_PEMblock indocs/deploy/index.htmlto the new public key. - Compute new form hash:
sha256sum docs/deploy/index.html
- Commit the HTML change on a branch. Push. Integrity check will fail on
DEPLOY_FORM_SHA256mismatch — this is expected. Note the new hash from the failed run's logs. - Settings → Variables → update
DEPLOY_FORM_SHA256to the new hash. CI re-runs and passes. - Upload new private key:
gh secret set PAYLOAD_PRIVATE_KEY --repo Palo-Cortex/secops-framework < /tmp/deploy_private.pem
- Destroy local copy:
shred -u /tmp/deploy_private.pem 2>/dev/null || rm -f /tmp/deploy_private.pem rm -f /tmp/deploy_public.pem
- Merge into
main.
After rotation, payloads that were dispatched against the old public key but not yet processed will fail to decrypt. In practice this is a non-issue because workflow queue depth is near-zero.
Quarterly, review the GitHub organization audit log (Settings → Security log at
the Palo-Cortex org level) for:
- Unexpected pushes to
main. - Secret access events on
PAYLOAD_PRIVATE_KEY. - Variable update events on
DEPLOY_FORM_SHA256/DEPLOY_WORKFLOW_SHA256. - New CODEOWNERS or branch protection rule changes.
- Failed
Integrity Checkruns (these are the canaries for unauthorized changes to the trust-boundary files).
When a second technical contributor joins the team, enable enforced
CODEOWNERS review on docs/deploy/index.html and .github/workflows/** and
add Require a pull request before merging with Require status checks to pass selecting Integrity Check / verify. This upgrades the trust-boundary
files from detection-only to prevention by requiring a second reviewer and
blocking merges until the integrity check passes.