Skip to content
Open
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
6 changes: 1 addition & 5 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
{
"image": "mcr.microsoft.com/devcontainers/universal:2",
"image": "mcr.microsoft.com/devcontainers/rust:1-bookworm",
"features": {
"ghcr.io/devcontainers/features/rust:1": {
"version": "latest",
"profile": "default"
},
"ghcr.io/devcontainers-contrib/features/vault-asdf:2": {
"version": "latest"
}
Expand Down
159 changes: 159 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
name: Security

on:
pull_request:
push:
branches: ["main"]
schedule:
- cron: "0 9 * * 1" # weekly Monday 09:00 UTC

permissions:
contents: read

jobs:
rust-checks:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Rust (stable)
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Rust cache
uses: Swatinem/rust-cache@v2

- name: fmt (fail if changed)
run: cargo fmt --all -- --check

- name: clippy
run: cargo clippy --all-targets --all-features -- -D warnings

- name: test
run: cargo test --all --all-features

Comment on lines +14 to +36
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rust-checks job duplicates functionality already present in the existing test.yml workflow. Both workflows run cargo fmt, clippy, and test on pull requests and pushes to main. This duplication increases CI runtime and maintenance burden. Consider removing this job from the security workflow and relying on the test workflow for these checks, or consolidate the workflows if security-specific context is needed for these checks.

Suggested change
rust-checks:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust (stable)
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Rust cache
uses: Swatinem/rust-cache@v2
- name: fmt (fail if changed)
run: cargo fmt --all -- --check
- name: clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: test
run: cargo test --all --all-features

Copilot uses AI. Check for mistakes.
dependency-audit:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Rust (stable)
uses: dtolnay/rust-toolchain@stable

- name: Rust cache
uses: Swatinem/rust-cache@v2

- name: Install cargo-audit
uses: taiki-e/install-action@v2
with:
tool: cargo-audit

- name: RustSec audit
run: cargo audit

dependency-policy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Rust (stable)
uses: dtolnay/rust-toolchain@stable

- name: Rust cache
uses: Swatinem/rust-cache@v2

- name: Install cargo-deny
uses: taiki-e/install-action@v2
with:
tool: cargo-deny

- name: cargo-deny (licenses, bans, sources, advisories)
run: cargo deny check

sbom:
runs-on: ubuntu-latest
permissions:
contents: read
Comment on lines +79 to +80
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The permissions setting here is redundant since the workflow already defines contents: read at the top level (line 11). Job-level permissions override top-level permissions, so this doesn't add security value and can be removed for simplicity. Only jobs that need different permissions (like trivy-fs which needs security-events: write) should explicitly set them.

Suggested change
permissions:
contents: read

Copilot uses AI. Check for mistakes.
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Rust (stable)
uses: dtolnay/rust-toolchain@stable

- name: Rust cache
uses: Swatinem/rust-cache@v2

- name: Install cargo-cyclonedx
uses: taiki-e/install-action@v2
with:
tool: cargo-cyclonedx

- name: Generate CycloneDX SBOM
run: |
mkdir -p artifacts
cargo cyclonedx --format json > artifacts/sbom.cdx.json
cargo cyclonedx --format xml > artifacts/sbom.cdx.xml

- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: artifacts/

secrets:
runs-on: ubuntu-latest
permissions:
contents: read
Comment on lines +110 to +111
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The permissions setting here is redundant since the workflow already defines contents: read at the top level (line 11). Job-level permissions override top-level permissions, so this doesn't add security value and can be removed for simplicity. Only jobs that need different permissions (like trivy-fs which needs security-events: write) should explicitly set them.

Copilot uses AI. Check for mistakes.
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITLEAKS_ENABLE_UPLOAD_ARTIFACT: "true"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

trivy-fs:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Trivy filesystem scan
uses: aquasecurity/trivy-action@0.28.0
with:
scan-type: fs
scan-ref: .
format: sarif
output: trivy-fs.sarif
ignore-unfixed: true

- name: Upload Trivy SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-fs.sarif

security-status:
runs-on: ubuntu-latest
needs: [rust-checks, dependency-audit, dependency-policy, sbom, secrets, trivy-fs]
if: always()
steps:
- name: Check all jobs status
run: |
if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition syntax here may not work as expected. The GitHub Actions expression contains(needs.*.result, 'failure') returns a boolean (true/false), but you're comparing it as a string "true" in bash. The double evaluation (GitHub Actions expression to string, then bash string comparison) is redundant. Consider using a simpler approach with the GitHub Actions if conditional directly on the needs context, or use a more standard pattern like checking the JSON array of results.

Suggested change
if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
if ${{ contains(needs.*.result, 'failure') }}; then

Copilot uses AI. Check for mistakes.
echo "One or more security checks failed"
exit 1
else
echo "All security checks passed"
fi
52 changes: 52 additions & 0 deletions deny.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# cargo-deny configuration
# See: https://embarkstudios.github.io/cargo-deny/

[advisories]
version = 2
# Don't warn on unmaintained packages yet
ignore = [
"RUSTSEC-2025-0134", # rustls-pemfile is unmaintained, comes from aws-sdk dependencies
]

[licenses]
# Accept common permissive licenses
allow = [
"MIT",
"Apache-2.0",
"BSD-3-Clause",
"BSD-2-Clause",
"ISC",
"Unicode-DFS-2016",
]
confidence-threshold = 0.8
exceptions = []

[licenses.private]
# Ignore private crates
ignore = false

[bans]
# Check for duplicate dependencies
multiple-versions = "warn"
# Wildcards not allowed in dependencies
wildcards = "deny"
# Highlight deprecated crates
highlight = "all"
workspace-default-features = "allow"
external-default-features = "allow"

# List specific crates to deny (e.g., known security issues)
deny = []

# Skip certain crates from duplicate checking
skip = []
skip-tree = []

[sources]
# Ensure all dependencies come from trusted sources
unknown-registry = "deny"
unknown-git = "deny"

[sources.allow-org]
# Allow crates from GitHub orgs
github = []
4 changes: 2 additions & 2 deletions src/backends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ mod secret_backend;
mod vault;

#[allow(unused_imports)] // Used in tests
pub use aws_secrets::{AwsSecretsClient, create_test_client};
pub use aws_secrets::{create_test_client, AwsSecretsClient};
pub use file::FileBackend;
pub use secret_backend::SecretBackend;
#[allow(unused_imports)] // Used in tests
pub use vault::{VaultBackend, VaultClient, SecretMetadata, VaultSecretData, VaultWriteRequest};
pub use vault::{SecretMetadata, VaultBackend, VaultClient, VaultSecretData, VaultWriteRequest};

/// Backend type enumeration
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down
2 changes: 1 addition & 1 deletion tests/api_target_tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use secret_rotator::targets::ApiTarget;
use secret_rotator::config::ApiTargetConfig;
use secret_rotator::targets::ApiTarget;

#[test]
fn test_build_url_with_placeholder() {
Expand Down
3 changes: 1 addition & 2 deletions tests/aws_secrets_tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use secret_rotator::backends::{AwsSecretsClient, create_test_client};
use aws_sdk_secretsmanager::types::Tag;
use secret_rotator::backends::{create_test_client, AwsSecretsClient};
use std::collections::HashMap;

#[test]
Expand Down Expand Up @@ -94,4 +94,3 @@ fn test_metadata_to_tags_empty() {
let tags = client.metadata_to_tags(&metadata);
assert!(tags.is_empty());
}

2 changes: 1 addition & 1 deletion tests/env_updater_tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use secret_rotator::env_updater::EnvUpdater;
use anyhow::Result;
use secret_rotator::env_updater::EnvUpdater;
use std::fs;
use tempfile::TempDir;

Expand Down
2 changes: 1 addition & 1 deletion tests/file_backend_tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use secret_rotator::backends::{FileBackend, SecretBackend};
use anyhow::Result;
use secret_rotator::backends::{FileBackend, SecretBackend};
use std::collections::HashMap;
use tempfile::TempDir;

Expand Down
2 changes: 1 addition & 1 deletion tests/rotation_tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use chrono::{Duration, Utc};
use secret_rotator::rotation::{generate_secret, needs_rotation};
use std::collections::HashMap;
use chrono::{Duration, Utc};

#[test]
fn test_generate_secret() {
Expand Down
2 changes: 1 addition & 1 deletion tests/vault_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use secret_rotator::backends::{VaultClient, SecretMetadata, VaultSecretData, VaultWriteRequest};
use secret_rotator::backends::{SecretMetadata, VaultClient, VaultSecretData, VaultWriteRequest};
use std::collections::HashMap;

#[test]
Expand Down
Loading