First — thank you. sealed-env is a small open-source project maintained by
David Almeida, and any review, PR, or
issue from outside eyes is genuinely valuable.
This document covers what you need to contribute productively. Read the sections relevant to what you want to do — you do not need to read the whole thing.
| What you want to do | Read |
|---|---|
| Report a non-security bug | Filing a bug |
| Report a security vulnerability | SECURITY.md — DO NOT open a public issue |
| Suggest a feature | Filing a feature request |
| Submit a code PR | Code contribution flow |
| Propose a change to the wire format | Spec changes |
| Add a new language adapter (Python / Go / Rust) | Adapter contributions |
| Improve docs / fix typos | Just open a PR — no ceremony needed |
sealed-env/
├── node/ Node.js implementation (TypeScript, zero deps)
├── java/ Multi-module Maven (sealed-env-core + spring-boot-starter)
├── docs/ Reading-order numbered guides (01-overview..06-format)
├── site/ Public landing page (GitHub Pages, plain HTML/CSS)
├── test-vectors/v1/ Cross-stack test vectors generated by Node, read by Java
├── assets/ Roman sigillum brand assets (SVGs)
├── SPEC.md Canonical .env.sealed v1 wire format specification
├── THREAT_MODEL.md Public threat model — what we defend against
├── SECURITY.md How to report vulnerabilities privately
└── README.md
cd node
npm install
npm run build
npm testRequires Node 20 or 22. No global tools. Tests run with the built-in
node --test runner.
cd java
./mvnw verify # if you have the Maven Wrapper
mvn -B verify # if you have Maven on PATHRequires JDK 17 or 21. The Java build also runs cross-stack interoperability
tests that consume the JSON vectors in test-vectors/v1/ — make sure those
are present (regenerate with node node/scripts/gen-test-vector.mjs).
The Java CI workflow regenerates the test vectors using Node before running
the Java build, so a change in either implementation that breaks
interoperability will be caught immediately. Run both sides locally before
pushing PRs that touch crypto or format code.
Use the Bug report issue template. The minimum we need to act:
- A minimal reproduction (10 lines of code or fewer ideally).
- The mode you're using (
basic,team, orenterprise). - Versions:
sealed-env(npm) orsealed-env-core(Maven), plus your Node version or JDK version. - OS: Linux distribution, macOS version, or Windows version.
- The exact error message you're seeing — full stack trace if Java.
⚠️ If the bug has security implications, do NOT file a public issue. See SECURITY.md for private disclosure.
Use the Feature request issue template. The minimum we need:
- The use case — what problem are you trying to solve, in your words.
- What you tried — current workarounds, why they don't work for you.
- Whether this would touch the wire format (a new metadata field, new mode, change to AAD construction, etc.). If yes, see Spec changes.
- Open an issue first for anything beyond a typo. Saves both of us the pain of you spending an afternoon on a PR I'll have to close.
- Fork → branch → PR as usual. One logical change per PR.
- Tests are required for code changes. The bar:
- New crypto code → unit tests + a cross-stack vector if the wire format is touched.
- New API surface → tests covering happy path + at least one error case.
- Bug fix → a regression test that fails before your fix and passes after.
- CI must be green before review. Run
npm testandmvn verifylocally first. - Sign your commits if you have GPG configured. Not required, but appreciated.
Loosely Conventional Commits. Prefixes I use:
feat:— a new user-facing capabilityfix:— a bug fixdocs:— documentation onlyrefactor:— code movement with no behavior changetest:— tests onlyci:— CI / GitHub Actions changeschore:— version bumps, deps, housekeeping
Scope optional: feat(java): ..., fix(node): ..., docs(readme): ....
The SEALED-ENV-V1 wire format is frozen. Files written today will be
readable forever. Spec changes require:
- An issue describing the change and the threat or capability it enables.
- A semver decision: backwards-compatible additions (new optional metadata)
are OK on
V1. Anything that would break existing readers requiresV2. - Both implementations updated in the same PR (Node and Java) plus updated test vectors covering the new behavior.
- A spec PR is always also a
THREAT_MODEL.mdPR — explain what the change does to the threat model.
If you're not sure whether something needs V2, open the issue and we'll
work it out.
This is a security tool. Changes that touch cryptographic code require extra care:
- No replacing primitives without discussion. AES-GCM, scrypt, Argon2id, HKDF-SHA256, HMAC-SHA256, and TOTP/RFC 6238 are pinned by SPEC. You can add support for additional KDFs (e.g. a future Argon2d) but you cannot remove or weaken existing ones in v1.
- No custom crypto. If you find yourself writing your own MAC, your own KDF, or your own padding scheme, stop and open an issue first.
- Constant-time comparisons are mandatory wherever a secret is compared.
Use
crypto.timingSafeEqual(Node) orMessageDigest.isEqual(Java). - Key wiping (
wipe(buf)in both implementations) must be called on derived keys after use. JVM/V8 may move memory around, but the hygiene matters.
If you're curious about a specific decision, the threat model document explains the reasoning — read it before proposing alternatives.
We'd love to see Python, Go, and Rust readers (and writers) of the
.env.sealed v1 format. Suggested approach:
- Read the SPEC end to end.
- Run the Java tests so you understand what passing the format conformance suite looks like.
- Use the test vectors in
test-vectors/v1/as your conformance bar. A new adapter MUST be able to decrypt every vector and produce the expected plaintext. - Open an issue before you start so we can coordinate (and so you don't end up duplicating work that's already in progress).
- Adapters live in the same monorepo under their own top-level
directory (
python/,go/,rust/) for now. We'll split into separate repositories if any grows large enough to need its own release cadence.
- Removing the threat model. It's a feature, not noise.
- Removing the public spec. Same reason.
- Adding a "convenience" mode that weakens defaults (e.g. a
--no-hmacflag forteammode). The whole point of mode tiers is that each tier provides an unbreakable floor. - Telemetry or analytics in the library. Period.
- Dependencies beyond what's already pinned, especially in the Node implementation, without strong justification.
This project follows the Contributor Covenant 2.1. Be kind, assume good faith, and remember that the person on the other end of the keyboard is trying to do their best.
If something is unclear in this document or in the code:
- For technical questions, open a Discussion.
- For private matters, email davidalmeidac@proton.me.