name: pqc-signatures-security description: Expert instructions to implement and verify ML-DSA-65 post-quantum cryptographic signatures and agentic workflow security.
PQC Signatures and Repository Security Agent Skill
Companion skill: pqc-secrets — API key encryption at rest via ML-KEM-768 + AES-256-GCM. Use both together for complete PQC coverage: pqc-secrets encrypts your secrets; this skill verifies your code integrity.
This skill provides comprehensive instructions, blueprints, and reference code for implementing post-quantum cryptographic (PQC) signature verification and agentic security standards in any repository.
AI agents using this skill are equipped to:
- Establish a post-quantum secure file-integrity framework.
- Auto-detect and enforce signature validation before executing code.
- Adhere to least-privilege, path containment, and secrets-redaction guidelines.
1. Core Philosophy: Hemispheric Protection
Every cryptographic implementation exists to protect data and sovereignty. Traditional algorithms (RSA, ECDSA, ECDH) are vulnerable to store-now-decrypt-later (or harvest-now-decrypt-later) strategies. The reduction in quantum attack resources requires proactive migration to NIST FIPS 204/203 standards:
- Digital Signatures: ML-DSA-65 (Module-Lattice-Based Digital Signature Algorithm).
- Key Encapsulation / Encryption: ML-KEM-768 or ML-KEM-1024.
- Integrity Hashing: SHA3-256 or SHA3-512.
- Symmetric Encryption: AES-256-GCM.
- Key Derivation: HKDF using SHA3-256.
2. Universal Agent Integration Protocol
Whenever a Large Language Model (LLM) or autonomous agent initializes inside a repository with this infrastructure:
- Auto-Detect: Look for the public key
AGENT_PUBKEY.pemin the root, along with corresponding.sigfiles. - Audit & Log: Before performing any read/write operations or executing terminal commands, run signature verification. Output results in a
<pqc_audit_log>block. - Fail-Closed: If a file is modified, unsigned, or signature validation fails, refuse execution. Immediately flag the issue to the human operator.
Example Agent Log Format
<pqc_audit_log>
[✓] Public Key Detected: AGENT_PUBKEY.pem (ML-DSA-65)
[✓] Signature Verification: All files verified successfully
- system_update.sh: Verified
- verify_signatures.sh: Verified
[✓] Cryptography Standards: PQC Enforced
</pqc_audit_log>
3. Infrastructure Implementation Blueprint
To re-implement the cryptographic signature verification system in any repository, follow these steps:
Step 1: Initialize Directories and Ignore Private Key
Create a .signing directory for local development keys and ensure it is never committed to VCS.
mkdir -p .signing
echo ".signing/" >> .gitignore
Step 2: Generate ML-DSA-65 Keypair
Using OpenSSL 3.5+ (or OpenSSL 3.x compiled with the oqs-provider):
# Generate the private key
openssl genpkey -algorithm ML-DSA-65 -out .signing/agent_privkey.pem
chmod 600 .signing/agent_privkey.pem
# Extract the public key to the repo root
openssl pkey -in .signing/agent_privkey.pem -pubout -out AGENT_PUBKEY.pem
Note: In production environments, store the private key in a KMS (e.g. AWS KMS, HashiCorp Vault) or HSM.
Step 3: Identify Files to Sign
Sign all executable scripts, shell scripts, system configuration files, and critical agent policies. Common files include:
*.sh,*.bash,*.zshAGENTS.md(Agent Security Policy)llms.txtor configuration files.
Step 4: Create a Verification Script (verify_signatures.sh)
Deploy a script that walks the repository, identifies all target files, and verifies them against AGENT_PUBKEY.pem.
#!/usr/bin/env bash
# verify_signatures.sh
set -euo pipefail
PUBKEY="AGENT_PUBKEY.pem"
verify_file() {
local file="$1"
local sig="${file}.sig"
if [[ ! -f "$file" ]]; then
echo "ERROR: File not found: $file" >&2
return 1
fi
if [[ ! -f "$sig" ]]; then
echo "MISSING SIGNATURE: $file" >&2
return 2
fi
if openssl pkeyutl -verify -pubin -inkey "$PUBKEY" -in "$file" -sigfile "$sig" 2>&1 | grep -q "Signature Verified Successfully"; then
echo "OK: $file"
return 0
else
echo "VERIFICATION FAILED: $file" >&2
return 1
fi
}
Step 5: Integrate Git Hooks
Install a post-merge hook so that files are automatically verified immediately upon a git pull or git merge.
# .git/hooks/post-merge
#!/usr/bin/env bash
set -euo pipefail
./verify_signatures.sh || { echo "Signature check failed. Aborting." >&2; exit 1; }
Step 6: Create the Signing Helper
Create .signing/sign_file.sh to allow maintainers to easily sign or re-sign files when modified.
# .signing/sign_file.sh
#!/usr/bin/env bash
set -euo pipefail
FILE="$1"
openssl pkeyutl -sign -inkey .signing/agent_privkey.pem -in "$FILE" -out "${FILE}.sig"
4. Secure Coding DNA Reference
Implementations must adopt secure programming practices to prevent injection, escalation, and leaks.
Pattern 1: Fail-Closed Signature Verification (Python)
Ensure Python scripts verify subprocesses, configs, or modules before running them.
import subprocess
from pathlib import Path
def verify_mldsa_signature(file_path: Path, sig_path: Path, pubkey_path: Path) -> bool:
if not file_path.is_file() or not sig_path.is_file() or not pubkey_path.is_file():
return False
result = subprocess.run(
["openssl", "pkeyutl", "-verify", "-pubin", "-inkey", str(pubkey_path),
"-in", str(file_path), "-sigfile", str(sig_path)],
check=False, text=True, capture_output=True
)
return result.returncode == 0 and "Signature Verified Successfully" in result.stdout
Pattern 2: Secrets Redaction
A pipeline filter that strips sensitive keys, credentials, and block certificates before passing outputs to logs or external processes.
import re
SECRET_PATTERNS = [
re.compile(r"AKIA[0-9A-Z]{16}"),
re.compile(r"(?i)(api[_-]?key|token|secret|password)\s*[:=]\s*['\"]?[^'\"\s]+"),
re.compile(r"-----BEGIN (?:[A-Z ]*)?PRIVATE KEY-----[\s\S]+?-----END (?:[A-Z ]*)?PRIVATE KEY-----")
]
def redact(text: str) -> str:
redacted = text
for pattern in SECRET_PATTERNS:
redacted = pattern.sub("[REDACTED]", redacted)
return redacted
Pattern 3: Path Containment (Safe Child Path)
Prevent path traversal attacks by resolving symlinks and asserting containment.
from pathlib import Path
def safe_child_path(base_dir: Path, user_path: str) -> Path:
base = base_dir.resolve()
target = (base / user_path).resolve()
if not target.is_relative_to(base):
raise ValueError("Path traversal blocked")
return target
Pattern 4: Parameterized Process Spawning
Never use shell=True or pass concatenated strings to the shell. Always use argument arrays.
# GOOD:
subprocess.run(["tar", "-xzf", "archive.tar.gz", "-C", "/tmp/extracted"])
# BAD:
# subprocess.run(f"tar -xzf archive.tar.gz -C {user_input}", shell=True)
Pattern 5: Argon2id Password Hashing
Always store passwords or sensitive verification tokens using memory-hard Argon2id parameters.
from argon2 import PasswordHasher
PASSWORD_HASHER = PasswordHasher(
time_cost=3,
memory_cost=65536,
parallelism=4,
hash_len=32,
salt_len=16,
)
§5 Algorithm Reference
PQC signature suite. All algorithms are FIPS-compliant (final since August 2024) and approved for NSA CNSA 2.0 use.
| Algorithm | Standard | Type | Status | Sizes (bytes) | Use |
|---|---|---|---|---|---|
| ML-DSA-65 | FIPS 204 | Lattice sig | Final Aug 2024 | pk 1952 / sig 3309 | Identity / primary signing |
| ML-DSA-87 | FIPS 204 | Lattice sig | Final Aug 2024 | pk 2592 / sig 4627 | Higher-security signing |
| SLH-DSA-SHA2-128s | FIPS 205 | Hash-based sig | Final Aug 2024 | pk 32 / sig 7856 | Backup / long-term |
| SLH-DSA-SHAKE-128s | FIPS 205 | Hash-based sig | Final Aug 2024 | pk 32 / sig 7856 | Backup (SHAKE variant) |
| SLH-DSA-SHA2-192s | FIPS 205 | Hash-based sig | Final Aug 2024 | pk 48 / sig 16272 | Higher-security backup |
| SLH-DSA-SHA2-256s | FIPS 205 | Hash-based sig | Final Aug 2024 | pk 64 / sig 29792 | Maximum-security backup |
| SHA3-256 | FIPS 202 | Hash | Standard | 32 B digest | Integrity / message digests |
| SHA3-512 | FIPS 202 | Hash | Standard | 64 B digest | Higher-security hashing |
| Argon2id | OWASP 2025 | Password KDF | Standard | 32+ B, t=3 m=64MB p=4 | Password-based KDF |
Bold = used in the default pqc-signatures configuration. Other
algorithms are available via explicit flags.
References:
- NSA CNSA 2.0
- NIST FIPS 204/205 (August 2024 — final)
- NIST FIPS 202 (SHA-3)
Forbidden algorithms for code signing / identity (audit contexts excepted): RSA, DSA, ECDSA, Ed25519, ECDSA-secp256k1, MD5, SHA-1.
Standard cryptography (TLS 1.3, SSH host keys via Ed25519, GPG, platform TLS) is fine for transport. The line: if it signs code that runs on the user's systems, or identifies the user, it uses PQC.
§6 Signing Workflow
6.1 Generate an ML-DSA-65 keypair
$ pqc-sign keygen --algorithm ml-dsa-65
Wrote public key to ~/.config/pqc-sigs/ml-dsa-65.pub (1952 B)
Wrote private key to macOS keychain (service: pqc-sigs, account: ml-dsa-65)
The public key is safe to commit. The private key is in the OS keychain.
6.2 Sign a file
$ pqc-sign sign --key ml-dsa-65 --input ./dist/index.js --output ./dist/index.js.sig
Signed ./dist/index.js (43,192 B input) -> ./dist/index.js.sig (3,309 B signature)
Audit: sign algorithm=ml-dsa-65 input=dist/index.js size=43192
The signature is a standalone file. The input file is unchanged.
6.3 Verify a signature
$ pqc-sign verify --key ml-dsa-65 --input ./dist/index.js --signature ./dist/index.js.sig
OK: signature valid (ml-dsa-65, dist/index.js)
$ echo $?
0
Exit 0 on valid signature, 1 on invalid or missing.
6.4 Hash-based backup signatures (SLH-DSA)
For long-term signatures (e.g., notarized releases, audit logs), generate a SLH-DSA keypair as a backup:
$ pqc-sign keygen --algorithm slh-dsa-sha2-128s
$ pqc-sign sign --key slh-dsa-sha2-128s --input ./dist/index.js
SLH-DSA signatures are 7,856 B (much larger than ML-DSA-65's 3,309 B) but are based purely on hash security — no lattice assumptions.
§7 Public Key Distribution
7.1 Trusted public key registry
Public keys are stored at ~/.config/pqc-sigs/<algorithm>.pub and
optionally in a per-repo .pqc-keys/ directory for project-scoped
trust.
$ ls .pqc-keys/
ml-dsa-65.pub # trusted ML-DSA-65 public key for this project
slh-dsa-sha2-128s.pub # backup SLH-DSA key
7.2 Commit .pqc-keys/
Commit .pqc-keys/*.pub to the repo. These are public keys — safe
to commit, and consumers of the project need them to verify
signatures.
Add .pqc-keys/*.priv (if any private key files exist locally) to
.gitignore.
7.3 Distribute public keys out-of-band
For trust bootstrapping, distribute public keys via a separate channel (e.g., a project website with HTTPS, a signed announcement in a mailing list). Never rely on a single channel for the first introduction of a public key.
§8 Verification Recipes
8.1 Pre-commit hook
#!/usr/bin/env bash
# .git/hooks/pre-commit
# Re-verify all signatures in the project before allowing commit.
set -e
for sig in $(find . -name '*.sig'); do
input="${sig%.sig}"
if ! pqc-sign verify --key ml-dsa-65 --input "$input" --signature "$sig" 2>/dev/null; then
echo "FAIL: $sig does not verify"
exit 1
fi
done
8.2 CI verification
# .github/workflows/verify.yml
name: verify
on: [push, pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pqc-sign
run: cargo install pqc-sign
- name: Verify all signatures
run: |
for sig in $(find . -name '*.sig'); do
input="${sig%.sig}"
pqc-sign verify --key ml-dsa-65 --input "$input" --signature "$sig"
done
8.3 Agent runtime check (Hermes MCP)
The betterbrowsermcp MCP server does not currently expose
pqc-sign tools. This is a v0.9.0 candidate. For now, agents
can shell out:
$ pqc-sign verify --key ml-dsa-65 --input ./dist/index.js --signature ./dist/index.js.sig
§9 Anti-pattern Catalog
9.1 Ed25519 / RSA / ECDSA code signing
# WRONG — classical signature, vulnerable to quantum attacks
$ gpg --sign --default-key 0xDEADBEEF dist/index.js
$ cosign sign-blob --key .cosign/key.pem dist/index.js
$ signify -S .signify/key.sec -m dist/index.js
Fix: use pqc-sign sign --key ml-dsa-65. Migrate any existing
Ed25519/RSA/ECDSA signing keys to ML-DSA-65 with a planned
rollover (sign the new ML-DSA-65 pubkey with the old key, then
distribute).
9.2 Signing the hash of a file (not the file itself)
# WRONG — vulnerable to chosen-prefix collisions if hash is weak
import hashlib
digest = hashlib.sha256(open("dist/index.js", "rb").read()).digest()
signature = pqc_sign(digest)
Fix: sign the file contents directly. The ML-DSA-65 / SLH-DSA implementations handle internal hashing correctly. Don't pre-hash externally — that bypasses the algorithm's domain separation.
9.3 Self-signed without a trust anchor
# WRONG — no way for the consumer to know they have the right pubkey
$ pqc-sign keygen --algorithm ml-dsa-65
$ pqc-sign sign --key ml-dsa-65 dist/index.js
# No pubkey distribution step!
Fix: commit .pqc-keys/ml-dsa-65.pub to the repo, publish it
on the project website, and reference it in the release notes.
9.4 Signing binaries that include the signature itself
# WRONG — chicken-and-egg
sig = sign(binary_with_placeholder_sig)
binary_with_real_sig = binary_with_placeholder.replace(placeholder, sig)
Fix: sign the binary without the signature, append the signature
as a separate file (binary.sig). The signature is over the
binary's exact bytes — adding the signature changes the bytes,
invalidating the signature.
9.5 Not rotating signing keys
Signing keys should be rotated annually (or after a security incident). The procedure:
- Generate new ML-DSA-65 keypair.
- Cross-sign: new key signs old pubkey; old key signs new pubkey.
- Distribute new pubkey in
.pqc-keys/. - Re-sign all artifacts with new key.
- Keep old key for 1-year grace (verify-old artifacts window).
§10 Threat Model Statement
Primary risk: code tampering
The dominant threat vector is code tampering — a malicious actor modifying signed artifacts (binaries, scripts, configs) between author and consumer. Signing detects this; verification enforces it.
What signing does NOT protect against
- Compromise of the signing key. If the keychain is compromised, the attacker can sign malicious updates. Mitigation: hardware keychain, signing ceremony review, key rotation.
- Vulnerabilities in the signed code itself. ML-DSA-65 verifies that the code is exactly what was signed, not that the code is safe. Use code review, fuzzing, and SAST in addition.
- Consumer side compromise. If the consumer's
pqc-sign verifybinary is replaced, the consumer is fooled. Use reproducible builds and signed binaries ofpqc-signitself.
What signing DOES protect against
- MITM substitution of the download. TLS handles transport; signing handles end-to-end authenticity.
- Compromise of the distribution channel. Even if the GitHub release is replaced, the signature won't match the consumer's trusted pubkey.
- Long-term integrity. SLH-DSA signatures remain valid indefinitely (hash security), so even if ML-DSA-65 is broken by a future quantum attack, the SLH-DSA backup still verifies.
In-memory reads are trusted
The user's own LLM (Hermes, Claude Code, etc.) is trusted to read
the local public key, run the verify command, and report the
result. The audit log captures every pqc-sign invocation so
the user can verify "did my agent sign/verify X at Y time?"
§11 See also
- pqc-secrets — companion skill for symmetric encryption of secrets (uses ML-KEM-768 + AES-256-GCM)
references/pqc-sign-cli.md— CLI reference (future work)references/signature-formats.md— signature file formats (future work)