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
9 changes: 9 additions & 0 deletions .github/workflows/release-config-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ jobs:
go-version: '1.25'
cache: true

- name: Install syft for SBOM generation
uses: anchore/sbom-action/download-syft@v0

- name: Run GoReleaser Snapshot
uses: goreleaser/goreleaser-action@v6
with:
Expand Down Expand Up @@ -166,6 +169,9 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Install syft for SBOM generation
uses: anchore/sbom-action/download-syft@v0

- name: Run GoReleaser Snapshot with Docker
uses: goreleaser/goreleaser-action@v6
with:
Expand Down Expand Up @@ -194,6 +200,9 @@ jobs:
go-version: '1.25'
cache: true

- name: Install syft for SBOM generation
uses: anchore/sbom-action/download-syft@v0

- name: Generate cask with GoReleaser snapshot
uses: goreleaser/goreleaser-action@v6
with:
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ jobs:
echo "=== Auth config structure (no secrets) ==="
cat ~/.docker/config.json | jq 'del(.auths[].auth) | del(.auths[].identitytoken)'

- name: Install cosign
uses: sigstore/cosign-installer@v3

- name: Install syft for SBOM generation
uses: anchore/sbom-action/download-syft@v0

- name: Create completions directory
run: mkdir -p completions

Expand All @@ -108,6 +114,39 @@ jobs:
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}

- name: Sign checksums with cosign (keyless)
run: |
VERSION="${{ github.ref_name }}"
VERSION_NO_V="${VERSION#v}"
CHECKSUMS_FILE="./dist/dsops_${VERSION_NO_V}_checksums.txt"
if [ ! -f "${CHECKSUMS_FILE}" ]; then
echo "Error: Checksums file not found: ${CHECKSUMS_FILE}"
echo "Available files in ./dist:"
ls -la ./dist/*.txt 2>/dev/null || echo "No .txt files found"
exit 1
fi
cosign sign-blob --yes "${CHECKSUMS_FILE}" \
--output-signature "${CHECKSUMS_FILE}.sig" \
--output-certificate "${CHECKSUMS_FILE}.pem"

- name: Sign Docker images with cosign (keyless)
run: |
VERSION="${{ github.ref_name }}"
VERSION_NO_V="${VERSION#v}"
# Sign both version-tagged and latest images
cosign sign --yes "ghcr.io/systmms/dsops:${VERSION_NO_V}"
# Only sign latest for non-prerelease versions
if [[ ! "${VERSION}" =~ (alpha|beta|rc) ]]; then
cosign sign --yes "ghcr.io/systmms/dsops:latest"
fi

- name: Upload signature artifacts
uses: softprops/action-gh-release@v2
with:
files: |
./dist/*.sig
./dist/*.pem

- name: Attest build provenance (checksums)
uses: actions/attest-build-provenance@v2
with:
Expand Down
7 changes: 7 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ checksum:
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
algorithm: sha256

# Software Bill of Materials (SBOM) generation
# Generates SPDX format SBOM for supply chain transparency
sboms:
- artifacts: archive
documents:
- "{{ .ProjectName }}_{{ .Version }}_sbom.spdx.json"

changelog:
use: github-native
sort: asc
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,8 @@ ADRs provide decision history and rationale for future maintainers. See `docs/ad
- N/A (stateless release infrastructure) (020-release-distribution)
- Go 1.25 (existing project), Bash (Makefile/CI), YAML (Lefthook config) + Lefthook (via npx - no global install required) (022-mod-tidy-check)
- N/A (no data persistence) (022-mod-tidy-check)
- Go 1.25 + GoReleaser v2, cosign (Sigstore), syft (SBOM), memguard (023-security-trust)
- N/A (documentation + CI/CD changes + runtime memory protection) (023-security-trust)

## Recent Changes
- 020-release-distribution: Added Go 1.25+ (matches existing project) + GoReleaser (v2.x), GitHub Actions, Docker
142 changes: 142 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Security Policy

## Supported Versions

dsops follows semantic versioning. Security updates are provided for:

| Version | Supported |
|---------|--------------------|
| 0.x | :white_check_mark: |

> As dsops is pre-1.0, all 0.x releases receive security updates. Once 1.0 is released, this table will specify which major/minor versions are actively supported.

## Reporting a Vulnerability

We take security vulnerabilities seriously. If you discover a security issue, please report it responsibly.

### Reporting Methods

1. **GitHub Security Advisories** (Preferred)
- Navigate to the [Security tab](https://github.com/systmms/dsops/security/advisories) in our repository
- Click "Report a vulnerability"
- This allows private discussion and coordinated disclosure

2. **Email**
- Send details to: security@systmms.com
- Use the subject line: `[dsops] Security Report: <brief description>`

### What to Include

- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Any suggested fixes (optional)

### Response Timeline

| Phase | Timeframe |
|-------|-----------|
| Initial acknowledgment | Within 48 hours |
| Severity assessment and timeline | Within 7 days |
| Fix development | Varies by complexity |
| Coordinated disclosure | 90 days from initial report |

We follow a 90-day coordinated disclosure policy. If a fix cannot be deployed within 90 days, we will work with the reporter on an appropriate disclosure timeline.

### What to Expect

1. **Acknowledgment**: You'll receive confirmation that we've received your report within 48 hours
2. **Assessment**: We'll evaluate the severity and determine a fix timeline within 7 days
3. **Updates**: We'll keep you informed of our progress
4. **Credit**: With your permission, we'll acknowledge your contribution in the release notes
5. **Disclosure**: Once a fix is released, we'll publish a security advisory

### Credit and Acknowledgment

We believe in recognizing the valuable contributions of security researchers. Unless you prefer to remain anonymous, we will:

- Credit you in the security advisory
- Include your name/handle in the release notes
- Add you to our [security acknowledgments](#acknowledgments) section
- Link to your profile or website (if provided)

**Hall of Fame Eligibility**: Reports that result in a fix for Critical or High severity issues will be highlighted in our Hall of Fame section.

**Anonymity**: If you prefer to remain anonymous, simply let us know in your report. We will never disclose reporter identities without explicit permission.

## Out of Scope

The following issues are generally considered out of scope:

- **Denial of Service (DoS)** without additional security impact
- **Social engineering** attacks against project maintainers
- **Physical attacks** against infrastructure
- **Attacks requiring physical access** to a user's device
- **Issues in dependencies** without a demonstrated attack vector in dsops
- **Theoretical vulnerabilities** without proof-of-concept
- **Issues in unmaintained versions**

If you're unsure whether an issue is in scope, please report it anyway. We'd rather receive reports that turn out to be low-risk than miss genuine vulnerabilities.

## Report Triage Process

When we receive a vulnerability report, it goes through the following triage process:

### Assessment Categories

| Category | Response | Timeline |
|----------|----------|----------|
| **Critical** | Immediate escalation, expedited fix | Fix within days |
| **High** | Prioritized for next release | Fix within 2 weeks |
| **Medium** | Scheduled for upcoming release | Fix within 30 days |
| **Low** | Added to backlog | Fix within 90 days |
| **Informational** | Documented for future reference | No fix required |
| **Invalid/Out of Scope** | Closed with explanation | N/A |

### Invalid Report Handling

If a report is determined to be invalid or out of scope:

1. We will respond within 7 days explaining why
2. We will provide guidance on what would make it a valid report (if applicable)
3. We welcome follow-up questions or additional information
4. Reporters can request reconsideration if they disagree with the assessment

Common reasons reports may be marked invalid:
- Vulnerability requires conditions that are outside our threat model
- Issue is a known limitation documented in our [threat model](https://github.com/systmms/dsops/blob/main/docs/content/security/threat-model.md)
- Report lacks sufficient detail to reproduce
- Issue has already been reported and is being addressed

We treat all reporters with respect, regardless of whether their report results in a fix.

## Security Contacts

The security team can be reached via:

- **Primary**: [GitHub Security Advisories](https://github.com/systmms/dsops/security/advisories)
- **Email**: security@systmms.com
- **PGP Key**: Available upon request for encrypted communications

> **Note**: The security@systmms.com alias is monitored by project maintainers. For routine questions, please use GitHub Discussions instead.

## Security Features

dsops is designed with security as a core principle. Key security features include:

- **Ephemeral-first design**: Secrets are injected directly into process environments, never written to disk by default
- **Automatic log redaction**: All logging uses `logging.Secret()` to automatically mask sensitive values
- **Process isolation**: Parent processes never see secret values; only child processes receive them
- **Memory protection**: Secret values are protected from memory dumps using mlock
- **Signed releases**: All release artifacts are signed using Sigstore cosign
- **Software Bill of Materials**: Every release includes an SBOM for dependency transparency

## Acknowledgments

We thank the following individuals and organizations for responsibly disclosing security issues:

*No vulnerabilities have been reported yet. This section will be updated as we receive and address reports.*

---

This security policy is based on [GitHub's recommended security policy template](https://docs.github.com/en/code-security/getting-started/adding-a-security-policy-to-your-repository) and [OWASP guidelines](https://owasp.org/www-project-vulnerability-disclosure/).
47 changes: 40 additions & 7 deletions cmd/dsops/commands/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
dserrors "github.com/systmms/dsops/internal/errors"
"github.com/systmms/dsops/internal/execenv"
"github.com/systmms/dsops/internal/resolve"
"github.com/systmms/dsops/internal/secure"
)

func NewExecCommand(cfg *config.Config) *cobra.Command {
Expand Down Expand Up @@ -105,17 +106,49 @@ Examples:

cfg.Logger.Info("Successfully resolved %d environment variables", len(environment))

// Wrap secrets in SecureBuffers for secure handling
// This ensures secrets are encrypted in memory until needed
secureEnv := make(map[string]*secure.SecureBuffer)
var wrapErrors []string

for name, value := range environment {
buf, err := secure.NewSecureBufferFromString(value)
if err != nil {
wrapErrors = append(wrapErrors, fmt.Sprintf("%s: %s", name, err))
continue
}
secureEnv[name] = buf
}

// Check for wrapping errors
if len(wrapErrors) > 0 {
// Cleanup any buffers created before error
for _, buf := range secureEnv {
buf.Destroy()
}
cfg.Logger.Error("Failed to secure %d variables:", len(wrapErrors))
for _, err := range wrapErrors {
cfg.Logger.Error(" %s", err)
}
return dserrors.UserError{
Message: fmt.Sprintf("Failed to secure %d variables", len(wrapErrors)),
Details: "This may indicate a memory protection issue",
Suggestion: "Try running with --debug for more information",
}
}

// Create executor
executor := execenv.New(cfg.Logger)

// Execute command
// Execute command with both Environment (for display) and SecureEnvironment (for execution)
options := execenv.ExecOptions{
Command: args,
Environment: environment,
AllowOverride: allowOverride,
PrintVars: printVars,
WorkingDir: workingDir,
Timeout: timeout,
Command: args,
Environment: environment, // Kept for --print display (masked)
SecureEnvironment: secureEnv, // Used for secure execution
AllowOverride: allowOverride,
PrintVars: printVars,
WorkingDir: workingDir,
Timeout: timeout,
}

return executor.Exec(ctx, options)
Expand Down
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.env
.netlify
.hugo_build.lock
hugo_stats.json
node_modules
public
resources
63 changes: 63 additions & 0 deletions docs/assets/scss/common/_custom.scss
Original file line number Diff line number Diff line change
@@ -1 +1,64 @@
// Put your custom SCSS code here

// Custom cards shortcode styles (Hextra-compatible)
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
margin: 1.5rem 0;
}

.card {
display: flex;
flex-direction: column;
padding: 1.25rem;
border: 1px solid var(--bs-border-color, #dee2e6);
border-radius: 0.5rem;
text-decoration: none;
color: inherit;
transition: border-color 0.2s, box-shadow 0.2s;

&:hover {
border-color: var(--bs-primary, #0d6efd);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
text-decoration: none;
color: inherit;
}
}

.card-icon {
margin-bottom: 0.75rem;

svg {
width: 1.5rem;
height: 1.5rem;
color: var(--bs-primary, #0d6efd);
}
}

.card-title {
margin: 0 0 0.5rem;
font-size: 1.1rem;
font-weight: 600;
}

.card-description {
margin: 0;
font-size: 0.9rem;
color: var(--bs-secondary-color, #6c757d);
}

// Custom hero shortcode styles
.hero {
text-align: center;
padding: 3rem 1rem;
margin-bottom: 2rem;
}

.hero-content {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 1rem;
margin-top: 1.5rem;
}
Loading