Skip to content

fix: prevent path traversal in sync state and improve empty output handling#264

Closed
n24q02m wants to merge 7 commits into
mainfrom
claude/resolve-issues-beta-release-SNEWV
Closed

fix: prevent path traversal in sync state and improve empty output handling#264
n24q02m wants to merge 7 commits into
mainfrom
claude/resolve-issues-beta-release-SNEWV

Conversation

@n24q02m
Copy link
Copy Markdown
Owner

@n24q02m n24q02m commented May 22, 2026

Description

This PR addresses a HIGH severity path-traversal vulnerability in the sync state module and improves consistency in empty output handling across multiple commands.

Changes

Security Fix: Path Traversal Prevention

  • internal/syncer/state.go: Enhanced sanitizeID() to block path traversal attacks by:

    • Collapsing .. sequences to _
    • Neutralizing null bytes and path separators
    • Handling edge cases (empty strings, single dots)
    • Now sanitizes both target and id parameters (previously only id was sanitized)

    This prevents untrusted input from escaping the ~/.skret/sync-state directory.

Output Handling Improvements

  • internal/cli/list.go: Modified printSecrets() to emit valid JSON ([]) on stdout even when no secrets are found, while still showing the "No secrets found" hint on stderr. This preserves the contract for scripts that parse JSON output.

  • internal/cli/env.go: Modified printEnvPairs() to emit valid JSON ({}) or YAML on stdout for empty results, while maintaining the stderr hint for human users.

Performance Optimizations

  • Pre-allocate slices with known capacity in:

    • internal/cli/env.go: getEnvPairs()
    • internal/cli/list.go: filterSecrets()
    • internal/cli/import.go: importOptions.run()
    • internal/provider/local/local.go: GetBatch()
    • internal/logging/redact.go: RedactingHandler.Handle()
  • internal/logging/redact.go: Optimize attribute handling by using variadic AddAttrs() instead of looping.

Type of Change

  • Bug fix (path traversal vulnerability + output consistency)
  • Performance improvement (slice pre-allocation)

Testing

  • Added comprehensive test cases in internal/syncer/state_test.go:

    • TestStatePathFor_BlocksPathTraversal: Verifies 8 different path traversal attack vectors are blocked
    • TestSaveSyncState_PathTraversal: End-to-end test confirming files are written only inside sync-state directory
  • Added test cases in internal/cli/internal_test.go:

    • TestPrintSecrets_EmptyJSON_HasStderrHintAndEmptyArray: Verifies JSON output with empty secrets
    • TestPrintSecrets_EmptyTable_NoBody: Verifies table format behavior
    • TestPrintEnvPairs_EmptyJSON_HasStderrHintAndEmptyObject: Verifies JSON env output
    • TestPrintEnvPairs_EmptyYAML_HasStderrHintAndEmptyDoc: Verifies YAML env output
    • TestPrintEnvPairs_EmptyDotenv_OnlyStderrHint: Verifies dotenv format behavior
  • Added test cases in internal/logging/logging_test.go:

    • TestRedactingHandler_ManyAttrs_PreservesOrder: Verifies attribute ordering with optimized path
    • TestRedactingHandler_NoAttrs: Verifies zero-attribute edge case

Checklist

  • My code follows the project's coding standards
  • I have commented my code where necessary (security-critical functions documented)
  • Added new tests for the changes (11 new test functions)
  • My changes generate no new warnings

https://claude.ai/code/session_0151QTTpVxuCRGyJwMS2NUXn

claude added 4 commits May 21, 2026 13:52
StatePathFor formatted target and id into the file name without
neutralising "..", path separators, or NULs. A malicious target or id —
reachable via the syncer's GitHub/dotenv code paths — could write outside
~/.skret/sync-state. sanitizeID now also collapses "..", NUL, and the
remaining separator characters, and is applied to target as well.

Adds table-driven traversal tests covering "..", separators, NUL byte,
empty/dot ids, plus a SaveSyncState test asserting nothing lands above
the sync-state directory.
Previously `skret list --format json` (and `skret env --format json|yaml`)
printed only `[]` / `{}` to stdout when no secrets existed, leaving users
unsure if the command actually ran. The valid JSON/YAML on stdout stays
unchanged so scripts keep working; we now also emit a "no secrets found"
hint on stderr in those cases.

Adds tests covering each format's empty-state output for both list and
env commands.
RedactingHandler.Handle built up the redacted attribute list with a
zero-cap slice and called Record.AddAttrs once per attribute. Sizing the
slice with r.NumAttrs() and passing it variadically eliminates the
per-attribute reallocations and the loop overhead in the hot logging
path.

Adds tests for the many-attrs and zero-attrs branches to keep the new
preallocation path covered.
`importOptions.run`, `local.Provider.GetBatch`, `filterSecrets`, and
`getEnvPairs` all grew their result slices from a nil header. Sizing
each from the known upper bound (len(secrets) / len(keys)) removes the
incremental reallocation and copy in the hot import/list/env paths.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

claude added 3 commits May 22, 2026 02:12
Empty commit to re-run the windows-latest test job, which failed once
while macOS and Ubuntu passed on identical platform-independent code —
consistent with the repo's known Windows CI flakiness.
Two issues surfaced by the windows-latest CI job:

1. The empty-state stderr hint added for `list`/`env` JSON & YAML output
   contradicted the existing TestListCmd_EmptyStateJSON contract, which
   deliberately keeps machine-readable JSON output free of stderr noise
   (paired with TestListCmd_EmptyState for the table-format hint).
   Reverted printSecrets/printEnvPairs to the prior behavior; the slice
   pre-allocations in those functions are kept. Removed the obsolete
   print tests that asserted the reverted behavior.

2. TestRunCmd_SimpleCommand executed the real `run` command inline. On
   Unix `run` replaces the process via syscall.Exec, terminating the
   test binary mid-suite — so every cli test ordered after it silently
   never ran on Linux/macOS, masking failures (which is why only Windows
   caught issue 1). It now drives `run` from a re-exec of the test
   binary so only the child process is replaced; 43 previously-skipped
   tests now run and pass.

Adds SaveSyncState/LoadSyncState error-path tests (mkdir failure,
directory-as-path read error, null hashes field).
The golangci-lint noctx linter rejects context-less os/exec.Command.
Switch the TestRunCmd_SimpleCommand re-exec to exec.CommandContext with
the test's context, matching the convention already used in the e2e
tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants