Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
pip install -e ".[dev]" --no-build-isolation

- name: Lint with ruff
run: pip install ruff && ruff check src/ --target-version py310
run: ruff check src/ --target-version py310

- name: Run tests
env:
Expand Down
64 changes: 0 additions & 64 deletions .github/workflows/release-audit.yml

This file was deleted.

6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ Thumbs.db
# Envault
.envault-audit.log
.envault.salt
.env

# QA/temp run artifacts
_qa*
qa_*
test-file.txt
.env.dev
.env.staging
.env.prod
Expand Down
34 changes: 34 additions & 0 deletions .secrets.baseline
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"version": "1.5.0",
"plugins_used": [
{
"name": "SecretKeywordDetector",
"version": "1.5.0",
"configuration": {
"excluded_keywords": [],
"excluded_files": [],
"keyword_exclude": []
}
},
{
"name": "HighEntropyStringDetector",
"version": "1.5.0",
"configuration": {
"entropy_limit": 3.0,
"excluded_patterns": [],
"path_patterns": []
}
}
],
"results": {
"src/envault/serve.py": [
{
"type": "Secret Keyword",
"line_number": 36,
"hashed_secret": "5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e",
"is_secret": false
}
]
},
"generated_at": "2026-06-30T00:00:00.000000Z"
}
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ Thanks for your interest in contributing!
2. Create a virtual environment: python -m venv .venv && source .venv/bin/activate
3. Install dev dependencies: pip install -e ".[dev]"
4. Run tests: pytest tests/ -v
5. Lint: uff check src/
5. Lint: ruff check src/

## Pull Requests

- Fork the repo and create a feature branch
- Add tests for any new functionality
- Ensure all existing tests pass
- Run uff check src/ --fix before committing
- Run ruff check src/ --fix before committing
- Keep PRs focused on a single change

## Reporting Issues
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ We aim to respond within 48 hours and will keep you updated on the fix.
## Security Best Practices

- Keep your dependencies up to date
- Use `pip audit` to check for known vulnerabilities
- Use `pip-audit` to check for known vulnerabilities
- Report any security concerns promptly
52 changes: 0 additions & 52 deletions _qa_repro_test.py

This file was deleted.

77 changes: 0 additions & 77 deletions _qa_test_scan.py

This file was deleted.

4 changes: 1 addition & 3 deletions src/envault/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ def log(
with open(self.log_path, "a") as f:
f.write(json.dumps(entry) + "\n")

def get_history(
self, key: str | None = None, action: str | None = None, limit: int = 50
) -> list[dict]:
def get_history(self, key: str | None = None, action: str | None = None, limit: int = 50) -> list[dict]:
"""Get audit history, optionally filtered by key and/or action."""
path = Path(self.log_path)
if not path.exists():
Expand Down
27 changes: 6 additions & 21 deletions src/envault/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ def __init__(self, valid_keys: str | list[str]) -> None:
def check(self, headers: dict[str, str]) -> AuthResult:
api_key = headers.get("X-Api-Key", "") or headers.get("X-API-KEY", "")
if not api_key:
return AuthResult.fail(
401, "Unauthorized: API key required (X-API-Key header)"
)
return AuthResult.fail(401, "Unauthorized: API key required (X-API-Key header)")

if api_key not in self._keys:
return AuthResult.fail(403, "Forbidden: invalid API key")
Expand Down Expand Up @@ -126,9 +124,7 @@ def __init__(
def check(self, headers: dict[str, str]) -> AuthResult:
auth_header = headers.get("Authorization", "")
if not auth_header.startswith("Bearer "):
return AuthResult.fail(
401, "Unauthorized: Bearer token required for OAuth2"
)
return AuthResult.fail(401, "Unauthorized: Bearer token required for OAuth2")

token = auth_header[len("Bearer ") :]

Expand Down Expand Up @@ -173,9 +169,7 @@ def _introspect(self, token: str) -> AuthResult:
"Content-Type": "application/x-www-form-urlencoded",
}
if self._client_id and self._client_secret:
creds = base64.b64encode(
f"{self._client_id}:{self._client_secret}".encode()
).decode()
creds = base64.b64encode(f"{self._client_id}:{self._client_secret}".encode()).decode()
headers["Authorization"] = f"Basic {creds}"

req = Request(url, data=body, headers=headers, method="POST")
Expand All @@ -195,9 +189,7 @@ def _validate_claims(self, token: str, claims: dict[str, Any]) -> AuthResult:
required = set(self._required_scope.split())
if not required.issubset(token_scopes):
missing = required - token_scopes
return AuthResult.fail(
403, f"Forbidden: missing scope(s): {', '.join(sorted(missing))}"
)
return AuthResult.fail(403, f"Forbidden: missing scope(s): {', '.join(sorted(missing))}")

# Audience check
if self._required_audience:
Expand All @@ -207,12 +199,7 @@ def _validate_claims(self, token: str, claims: dict[str, Any]) -> AuthResult:
if self._required_audience not in audiences:
return AuthResult.fail(403, "Forbidden: invalid audience")

identity = (
claims.get("sub")
or claims.get("email")
or claims.get("client_id")
or "oauth2:user"
)
identity = claims.get("sub") or claims.get("email") or claims.get("client_id") or "oauth2:user"

# Cache the successful result
self._cache[token] = (identity, time.monotonic() + self._cache_ttl)
Expand All @@ -226,9 +213,7 @@ class MultiAuth:
If no backend is configured, all requests are allowed (open mode).
"""

def __init__(
self, backends: list[BearerAuth | ApiKeyAuth | OAuth2Auth] | None = None
) -> None:
def __init__(self, backends: list[BearerAuth | ApiKeyAuth | OAuth2Auth] | None = None) -> None:
self._backends = backends or []

@property
Expand Down
6 changes: 1 addition & 5 deletions src/envault/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,5 @@ def format_backup_list(entries: list[BackupEntry]) -> str:
lines = []
for entry in entries:
enc_tag = " [encrypted]" if entry.encrypted else ""
lines.append(
f" {entry.name}{enc_tag}\n"
f" Source: {entry.source_file}\n"
f" Created: {entry.timestamp}"
)
lines.append(f" {entry.name}{enc_tag}\n Source: {entry.source_file}\n Created: {entry.timestamp}")
return "\n".join(lines)
Loading
Loading