Skip to content

Commit 1400378

Browse files
committed
Merge branch 'develop'
2 parents 77fb62a + 2efd347 commit 1400378

28 files changed

Lines changed: 10249 additions & 2 deletions

.github/workflows/ci.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# CI for rpass — build & test the Rust password manager.
2+
name: CI
3+
4+
on:
5+
push:
6+
branches: [develop, main]
7+
pull_request:
8+
9+
concurrency:
10+
group: ci-${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
env:
14+
CARGO_TERM_COLOR: always
15+
RUST_BACKTRACE: 1
16+
17+
jobs:
18+
test:
19+
name: cargo test (${{ matrix.os }})
20+
runs-on: ${{ matrix.os }}
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
include:
25+
- os: ubuntu-latest
26+
rust: stable
27+
- os: macos-latest
28+
rust: stable
29+
30+
steps:
31+
- name: Checkout
32+
uses: actions/checkout@v4
33+
34+
- name: Install Rust toolchain
35+
uses: dtolnay/rust-toolchain@master
36+
with:
37+
toolchain: ${{ matrix.rust }}
38+
components: rustfmt, clippy
39+
40+
- name: Cache dependencies
41+
uses: Swatinem/rust-cache@v2
42+
with:
43+
shared-key: rpass
44+
45+
- name: Check formatting
46+
run: cargo fmt --all -- --check
47+
48+
- name: Clippy
49+
# Warnings reported but non-fatal until the codebase is fully clean.
50+
run: cargo clippy --all-targets --all-features -- -W clippy::all
51+
52+
- name: Run tests
53+
run: cargo test --all-features --verbose
54+
55+
- name: Release build
56+
run: cargo build --release --verbose
57+
58+
- name: CLI smoke (help / version)
59+
shell: bash
60+
run: |
61+
set -euo pipefail
62+
BIN=./target/release/rpass
63+
"$BIN" --help
64+
"$BIN" --version
65+
"$BIN" generate --length 16
66+
RPASS_LANG=en "$BIN" generate --passphrase --words 4
67+
RPASS_LANG=fr "$BIN" strength 'TestOnly-NotASecret-123!'
68+
69+
# Fast Linux-only path often used as required status check
70+
test-linux:
71+
name: cargo test (linux required)
72+
runs-on: ubuntu-latest
73+
steps:
74+
- uses: actions/checkout@v4
75+
76+
- name: Install Rust
77+
uses: dtolnay/rust-toolchain@stable
78+
79+
- uses: Swatinem/rust-cache@v2
80+
with:
81+
shared-key: rpass
82+
83+
- name: Test
84+
run: cargo test --all-features
85+
86+
- name: Build
87+
run: cargo build --all-features

.github/workflows/release.yml

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
name: Release
2+
3+
on:
4+
release:
5+
types: [created]
6+
workflow_dispatch: # Permet de déclencher manuellement pour tester
7+
8+
permissions:
9+
contents: write
10+
11+
env:
12+
CARGO_TERM_COLOR: always
13+
RUST_BACKTRACE: 1
14+
BIN_NAME: rpass
15+
16+
jobs:
17+
build:
18+
name: Build ${{ matrix.target }}
19+
runs-on: ${{ matrix.os }}
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
include:
24+
- target: x86_64-unknown-linux-gnu
25+
os: ubuntu-latest
26+
archive_ext: tar.gz
27+
28+
- target: aarch64-unknown-linux-gnu
29+
os: ubuntu-latest
30+
archive_ext: tar.gz
31+
cross: true
32+
33+
- target: x86_64-apple-darwin
34+
os: macos-latest
35+
archive_ext: tar.gz
36+
37+
- target: aarch64-apple-darwin
38+
os: macos-latest
39+
archive_ext: tar.gz
40+
41+
steps:
42+
- name: Checkout
43+
uses: actions/checkout@v4
44+
45+
- name: Install Rust toolchain
46+
uses: dtolnay/rust-toolchain@stable
47+
with:
48+
targets: ${{ matrix.target }}
49+
50+
- name: Cache dependencies
51+
uses: Swatinem/rust-cache@v2
52+
with:
53+
shared-key: rpass-release-${{ matrix.target }}
54+
55+
- name: Install cross (for aarch64 Linux)
56+
if: matrix.cross
57+
run: cargo install cross --git https://github.com/cross-rs/cross
58+
59+
- name: Build release binary (native)
60+
if: '!matrix.cross'
61+
run: cargo build --release --all-features --target ${{ matrix.target }}
62+
63+
- name: Build release binary (cross)
64+
if: matrix.cross
65+
run: cross build --release --all-features --target ${{ matrix.target }}
66+
67+
- name: Package binary
68+
shell: bash
69+
run: |
70+
set -euo pipefail
71+
STAGING="${BIN_NAME}-${{ matrix.target }}"
72+
mkdir -p "$STAGING"
73+
74+
cp "target/${{ matrix.target }}/release/${BIN_NAME}" "$STAGING/"
75+
cp README.md "$STAGING/" 2>/dev/null || true
76+
cp LICENSE* "$STAGING/" 2>/dev/null || true
77+
78+
tar -czf "${STAGING}.tar.gz" "$STAGING"
79+
echo "ASSET=${STAGING}.tar.gz" >> "$GITHUB_ENV"
80+
81+
- name: Smoke test binary
82+
shell: bash
83+
run: |
84+
set -euo pipefail
85+
BIN="target/${{ matrix.target }}/release/${BIN_NAME}"
86+
"$BIN" --version
87+
"$BIN" --help
88+
89+
- name: Upload artifact
90+
uses: actions/upload-artifact@v4
91+
with:
92+
name: ${{ env.ASSET }}
93+
path: ${{ env.ASSET }}
94+
95+
publish:
96+
name: Attach binaries to release
97+
needs: build
98+
runs-on: ubuntu-latest
99+
steps:
100+
- name: Download all artifacts
101+
uses: actions/download-artifact@v4
102+
with:
103+
path: artifacts
104+
105+
- name: Flatten artifacts
106+
run: |
107+
mkdir -p dist
108+
find artifacts -type f -name "*.tar.gz" -exec cp {} dist/ \;
109+
ls -la dist
110+
111+
- name: Upload to release
112+
uses: softprops/action-gh-release@v2
113+
with:
114+
files: dist/*.tar.gz
115+
env:
116+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

AGENTS.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# AGENTS.md — rpass
2+
3+
CLI password manager (Rust). Optimize for short context: read this first, then only the files you need.
4+
5+
## Stack
6+
7+
- Rust edition **2021**, package `rpass` (`src/lib.rs` + bin `src/main.rs`)
8+
- CLI: **clap** derive · crypto: **argon2** + **aes-gcm** + **rand** · secrets wipe: **zeroize**
9+
- Errors: **thiserror** (`error::Error` / `error::Result`) — avoid bare `unwrap` in lib paths
10+
- Serde JSON vault file · config TOML · storage under XDG dirs
11+
12+
## Layout (where to edit)
13+
14+
| Path | Responsibility | Touch when… |
15+
|------|----------------|-------------|
16+
| `src/cli/mod.rs` | dispatch `run()` | wire new commands |
17+
| `src/cli/args.rs` | clap structs / enums | flags, subcommands |
18+
| `src/cli/handlers.rs` | `cmd_*` handlers | command behaviour |
19+
| `src/cli/prompt.rs` | master-password prompts, `open_vault` | UX prompts |
20+
| `src/cli/clipboard.rs` | clipboard copy / clear | clipboard UX |
21+
| `src/cli/repl.rs` | interactive mode | REPL commands |
22+
| `src/vault.rs` | unlocked vault API, CRUD, audit, import/export, tables | business ops on entries |
23+
| `src/crypto.rs` | KDF, AES-GCM, password gen, TOTP, strength, HIBP, b64 | crypto behaviour |
24+
| `src/wordlist.txt` | passphrase dictionary | Diceware words |
25+
| `src/storage.rs` | FileStore, seal/unseal, session, backups, paths | persistence / session / paths |
26+
| `src/models.rs` | `Entry`, `VaultData`, `MasterKey`, audit types | data shape / serde fields |
27+
| `src/config.rs` | TOML config + env + `VaultPrefs` | defaults, keys, multi-vault map |
28+
| `src/error.rs` | error enum (`Display` via i18n) | new failure modes |
29+
| `src/i18n.rs` | EN/FR UI language (`t` / `tfmt!` / `init`) | new UI strings |
30+
| `src/main.rs` | parse → `cli::run`, exit codes | exit mapping only |
31+
| `tests/vault_flow.rs` | library lifecycle tests | vault API regressions |
32+
| `tests/integration.rs` | binary smoke tests | CLI surface smoke |
33+
| `completions/*` | shell completion scripts | new top-level commands |
34+
35+
Do **not** invent parallel storage layers. Extend `VaultStore` / `FileStore` if needed.
36+
37+
## Design invariants (do not break)
38+
39+
1. **Disk always encrypted**: plaintext `VaultData` exists only in memory after unlock. Mutate → `Vault::save()``storage::seal` (AES-GCM, **fresh nonce** each write).
40+
2. **File format** `EncryptedVault`: `version`, `salt` (b64), `nonce` (b64), `ciphertext` (b64+tag), `written_at`. Atomic write: `*.tmp` + rename; Unix mode `0600`.
41+
3. **Key hierarchy**: master password + salt → Argon2id (`hash_password_into`) → 32-byte `MasterKey` (zeroized).
42+
Session (P0): master key scellée dans `data_dir/.{name}.session` ; **wrap key dans `runtime_dir/{name}.wrap`** (`$RPASS_RUNTIME_DIR` ou `$XDG_RUNTIME_DIR/rpass`, sinon `data_dir/runtime/`). Jamais les deux dans le même fichier. `vault_path` validé ; `is_session_active` ne déchiffre pas / ne prolonge pas.
43+
4. **Decrypt errors**: `crypto::decrypt``DecryptFailed` ; `storage::unseal` (MDP saisi) mappe vers `WrongMasterPassword`. Session invalide → purge + `VaultLocked`.
44+
5. **Write lock**: `FileStore::save_encrypted` prend un lock coopératif `{name}.vault.lock` (PID) ; erreur `VaultBusy` si concurrent vivant.
45+
6. **Entry keys** in map: lowercase service name (`VaultData::entry_key`). Display name keeps original casing.
46+
7. **Password change** goes through `Entry::set_password` (history, max 10, updates `password_changed_at`).
47+
8. **Unlock**: `Vault::open_or_prompt` / session first — do not require password if session valid.
48+
9. **Clipboard**: effacement non garanti en CLI one-shot ; `RPASS_CLIPBOARD_WAIT=1` force l'attente. REPL OK.
49+
10. **No exploit PoCs, no weak crypto “for tests”** in production paths. Tests may use temp dirs via `RPASS_DATA_DIR` / `RPASS_CONFIG` / `RPASS_RUNTIME_DIR`.
50+
51+
## Default paths
52+
53+
- Vault: `$RPASS_DATA_DIR` or `dirs::data_dir()/rpass/{name}.vault` → typically `~/.local/share/rpass/default.vault`
54+
- Config: `$RPASS_CONFIG` or `~/.config/rpass/config.toml`
55+
- Session meta: data dir `.{name}.session`
56+
- Session wrap key: runtime dir `{name}.wrap`
57+
- Backups: data dir `backups/{name}_{timestamp}.vault.bak`
58+
- Env overrides: `RPASS_VAULT`, `RPASS_VAULT_PATH`, `RPASS_DATA_DIR`, `RPASS_RUNTIME_DIR`, `RPASS_TIMEOUT`, `RPASS_PASSWORD_LENGTH`, `RPASS_LANG` (`en`/`fr`), `RPASS_CLIPBOARD_WAIT`, `NO_COLOR`
59+
60+
## Commands map (CLI ↔ logic)
61+
62+
- Vault mgmt: `init|unlock|lock|change-master-password|status|destroy``Vault::*` + session in `storage`
63+
- CRUD: `add|get|show|list|update|delete|search``Vault` + `vault::print_*`
64+
- Gen: `generate` / auto on `add``crypto::generate_password` / `vault::gen_password_from_opts`
65+
- Audit: `audit|check-breach|strength|expire``Vault::audit`, `crypto::{check_breach,password_strength}`
66+
- TOTP: `add-totp|totp``Vault::{add_totp,totp}` / `crypto::generate_totp*`
67+
- I/O: `export|import|backup|restore` → vault import/export helpers + `storage::{create_backup,restore_backup}`
68+
- Config/REPL/completions: `config|*`, `interactive`, `completions`
69+
70+
Global flag: `--vault <name>` (not short `-V`; version uses `-V`).
71+
72+
## Conventions
73+
74+
- Errors: add variants in `error.rs`, return `Result<T>`, map at boundaries in `cli.rs`.
75+
- UI strings: English default; French via `RPASS_LANG=fr` / `config.language` using `crate::i18n::{t,tfmt,init}`.
76+
- Display tables: `colored` / `comfy-table`; respect password masking unless `--show`.
77+
- Clipboard: best-effort (`arboard`); failure must not abort core ops.
78+
- Sensitive types: `MasterKey`, `MasterPassword` use `Zeroize`/`ZeroizeOnDrop` — keep secrets out of `Debug` logs.
79+
- Prefer small functions; keep clap structs and handlers in `cli.rs` (large file — jump via command name / `fn cmd_`).
80+
- Imports: std → external → `crate::`.
81+
82+
## Verify before done
83+
84+
```bash
85+
cargo test
86+
cargo build # or --release for CLI smoke
87+
./target/debug/rpass --help
88+
```
89+
90+
Targeted:
91+
92+
```bash
93+
cargo test --test vault_flow
94+
cargo test --lib crypto
95+
```
96+
97+
Need isolated vault in tests: set `RPASS_DATA_DIR` + `RPASS_CONFIG` to a temp dir (see `tests/vault_flow.rs`).
98+
99+
## Out of scope (unless asked)
100+
101+
Multi-user/sync-cloud, team hybrid crypto, full i18n, generated man pages, clap_complete crate (completions are static under `completions/`).
102+
103+
## Token hygiene for agents
104+
105+
- Prefer **surgical reads** of one module; for CLI: `args.rs` (flags) → `handlers.rs` (`fn cmd_`) → `repl.rs` if REPL.
106+
- After behavior change in vault/crypto/storage: update or add a test in `tests/vault_flow.rs` or unit tests in the same module.
107+
- Do not re-document the whole README in PRs; change README only if user-facing paths/commands change.
108+
- Avoid drive-by refactors and new heavy deps without need.

0 commit comments

Comments
 (0)