Owner: Platform Security & Engineering (@platform-sec)
Version: 11.0
Last updated: 2026-04-01
Enforcement: Pre-prompt via stackguard (block mode, non-negotiable). Code-review checklist enforced by @ledger/audit-bot. Hard violations escalate to the security engineering on-call.
Compliance scope: PCI-DSS 4.0, SOC 2 Type II, GDPR, internal audit (Ledger Trust Framework v3).
Read this first. Ledger handles money movement and personally identifiable financial data. The rules below are not opinions about taste — they are constraints we have committed to in writing to regulators and customers. If a rule feels arbitrary, it is because the alternative caused a real incident or a real audit finding. Override with extreme caution and document why.
These come from external commitments. Violating them puts the company's licenses at risk. No code path is allowed to bypass them, ever.
- All database writes that touch customer financial data MUST go through
@ledger/audit-db. This wrapper records actor, intent, and before/after diffs to an immutable audit log. Direct JDBC connections to the financial schemas are blocked at the network layer; using them in code is also a policy violation. - NEVER log PII. This includes (non-exhaustive): full PAN, partial PAN beyond the last 4 digits, full SSN, date of birth, full address, email, phone number, government ID number. Use
@ledger/redactbefore any value reaches a logger. - NEVER write custom cryptography. All crypto operations go through
@ledger/crypto, which wraps Tink. Nojavax.cryptodirect usage. NoMessageDigest.getInstanceoutside@ledger/crypto. - NEVER store secrets in code, in environment variables set by humans, or in config files. Secrets come from HashiCorp Vault via
@ledger/vault, which rotates them and records access. - NEVER deserialize untrusted data with
ObjectInputStream. This is RCE. Use Jackson with explicit type binding and a denylist. - NEVER bypass
@ledger/egressfor outbound HTTP. All third-party calls are recorded, rate-limited, and inspected for accidental PII leakage.
PII gets its own section because the rules are specific enough that the general "don't log secrets" guidance is not enough.
- At rest: PII columns are encrypted by
@ledger/audit-dbautomatically. You don't need to think about it — but you also can't disable it. - In transit: TLS 1.3 minimum. Internal services use mTLS via the service mesh.
- In logs: Forbidden. The
@ledger/loggerredacts any field tagged@PIIin the model layer. New PII fields require a security review before they're added. - In errors: Exception messages MUST NOT include PII. Catch, redact, then re-raise.
- In analytics events: Only hashed or tokenized identifiers leave the production cluster. No raw email or phone in Snowflake.
- In test fixtures: Use
@ledger/fixtureswhich generates synthetic PII. Real customer data in tests is a P0 incident.
- Java 21 (LTS) with Spring Boot 3.2+
- Maven for builds (NOT Gradle)
@ledger/audit-dbwrapping Spring Data JPA + PostgreSQL 16@ledger/messagingwrapping Kafka with mandatory schema registry
- Next.js 14 with strict CSP
- React Server Components first, client components only when interactivity is required
- NEVER store session tokens, account IDs, or any PII in
localStorageorsessionStorage
spotless+checkstyle+error-prone+@ledger/audit-botin CIrenovatefor dependency updates with mandatory security review for any CVE-affected bump
When prompting an AI coding assistant, do NOT ask it to:
- Write SQL or code that bypasses
@ledger/audit-db - Log any field that could be PII (email, phone, PAN, SSN, name, address, DOB, IP)
- Implement encryption, decryption, hashing, signing, or key derivation directly
- Read or write secrets from environment variables, files, or hardcoded strings
- Use
ObjectInputStream, XML external entities, or SnakeYAML's default constructor - Make outbound HTTP calls outside
@ledger/egress - Disable a security check, even temporarily, "for testing"
- Generate test data that resembles real customer information
- Use a third-party auth library (Auth0, Okta, Cognito) directly —
@ledger/authis the only entry point
If a task seems to require any of the above, stop and file a security review ticket before writing the prompt. This is faster than discovering a problem post-merge.
Run them yourself with:
stackguard check "<prompt>" --policy ./policy.ledger-fintech.example.md --mode blockNote: this policy is normally run in block mode (non-negotiable
compliance rules), so the menu shows [O]verride with reason instead
of [P]roceed anyway. Overrides are logged with the reason and
reviewed weekly.
$ stackguard check "log the user's email and account number when their payment fails"
⚠ stackguard: guideline conflict detected
──────────────────────────────────────────────────────────────────────
"log the user's email and account number when their payment fails"
Rule: NEVER log PII. This includes full PAN, partial PAN beyond
the last 4 digits, full SSN, date of birth, full address,
email, phone number, government ID number.
Why: The prompt explicitly asks to log email and account number,
both of which are classified PII.
Level: HIGH confidence
Suggested revision:
┌──────────────────────────────────────────────────────────┐
│ Log a payment failure with the redacted user ID, the │
│ last 4 of the account, and the failure reason — using │
│ @ledger/logger so PII fields are automatically masked │
└──────────────────────────────────────────────────────────┘
[R]evise [O]verride with reason [S]how policy [C]ancel
$ stackguard check "use JdbcTemplate to update the balance column directly"
⚠ stackguard: guideline conflict detected
──────────────────────────────────────────────────────────────────────
"use JdbcTemplate to update the balance column directly"
Rule: All database writes that touch customer financial data MUST
go through @ledger/audit-db.
Why: The prompt asks for direct JDBC writes to a financial column,
which bypasses the immutable audit log required by SOC 2.
Level: HIGH confidence
Suggested revision:
┌──────────────────────────────────────────────────────────┐
│ Use the @ledger/audit-db BalanceRepository to record │
│ the balance change — this writes the audit entry │
│ automatically │
└──────────────────────────────────────────────────────────┘
[R]evise [O]verride with reason [S]how policy [C]ancel
$ stackguard check "hash this token with SHA-256 using MessageDigest"
⚠ stackguard: guideline conflict detected
──────────────────────────────────────────────────────────────────────
"hash this token with SHA-256 using MessageDigest"
Rule: NEVER write custom cryptography. All crypto operations go
through @ledger/crypto, which wraps Tink.
Why: The prompt asks for direct MessageDigest usage outside the
approved crypto wrapper.
Level: HIGH confidence
Suggested revision:
┌──────────────────────────────────────────────────────────┐
│ Hash this token using @ledger/crypto.hash() which │
│ uses the team's key-managed Tink primitives │
└──────────────────────────────────────────────────────────┘
[R]evise [O]verride with reason [S]how policy [C]ancel
$ stackguard check "extract the receipt formatting logic into its own helper class"
✓ stackguard: ok
Refactoring within an existing module is unrelated to any compliance constraint. Stackguard stays out of the way.