Commit Graph

16 Commits

Author SHA1 Message Date
Peter Steinberger
36dd0888a3
fix(root): align pinned fallback path boundary checks 2026-05-08 02:43:24 +01:00
Sarah Fortune
4a1e5d8b72 fix(root): drop spurious ".." prefix rejection on posix write path
The posix write helper rejected any path whose computed
path.relative(root, resolved) string-began with "..", even when the
resolved path was fully inside the root. That matched literal
filenames whose first segment merely starts with the two characters
".." (e.g. "..%2fpwned.txt", "..%00/pwned.txt") and produced an
"outside-workspace" error for paths that do not actually escape
the root. The real boundary is already enforced upstream by
resolvePathInRoot's path.resolve + isPathInside check, so the
extra startsWith("..") guard added no security value while
introducing platform divergence (windows did not have it). Drop the
guard, keep the path.isAbsolute check.

Recategorize the three former SAFE_REJECTED_SUSPICIOUS_WRITE_PAYLOADS
test inputs so the test reflects what each platform actually does:

- "..%2fpwned.txt" and "..%00/pwned.txt" are literal names that
  resolve fully inside root on both platforms; move them to
  LITERAL_SUSPICIOUS_WRITE_PAYLOADS (accepted everywhere).
- "..\pwned.txt" is a real traversal on windows where "\\" is a
  separator, but a literal filename on posix where "\\" is a regular
  name character; move it to POSIX_LITERAL_SUSPICIOUS_WRITE_PAYLOADS
  (accepted on posix, rejected on windows).

The literal-directory check in the same test uses fsp.stat on
windows since safeRoot.list goes through the pinned helper that is
unavailable on win32, matching the pattern already used a few lines
up. Bump the per-test timeout to 15s for slow windows fs under
parallel test load.

Drop a stale explanatory comment in expandHomePrefix.

After: 254 passed, 0 failed, 66 skipped on windows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:16:50 -07:00
Sarah Fortune
3be7ba6ee3 ci+test: run check on windows and guard windows-only test behavior
Run the check job on windows-latest in addition to ubuntu so the
windows code paths (no O_NOFOLLOW, node fallbacks for fd-relative
ops, ACL inspection) are exercised on every PR rather than only
documented.

Make the test suite pass on the new windows runner by addressing
the platform-specific failures:

- Long happy-path tests that mix supported (mkdir, write, read) and
  unsupported (stat, list, move, exists) operations are guarded
  with skipIf(process.platform === "win32") since the pinned
  filesystem helper throws "unsupported-platform" on win32 by
  design (src/pinned-python.ts).
- Short focused tests where the unsupported operation is the whole
  point (pinned-python, pinned-write-fallback-coverage,
  write-boundary-bypass symlink-move) split into runIf(non-win32)
  and runIf(win32) tests, with the windows variant asserting
  unsupported-platform.
- The expectFsSafeCode helper accepts unsupported-platform on
  windows; new expectedFsSafeCode helper substitutes for
  per-rejects.toMatchObject sites where the windows code differs
  from posix (e.g. path-alias / not-found returning
  unsupported-platform via the helper layer).
- secure-file-reads test split into a posix happy-path runIf and a
  windows runIf that asserts permission-unverified, since ACL
  inspection has no portable equivalent on windows
  (src/secure-file.ts:177).
- safeFileURLToPath test uses hardcoded platform-specific input/
  output instead of building the URL via pathToFileURL+fileURLToPath
  so the assertion verifies the function directly.
- Fix expandHomePrefix to normalize path separators by splitting via
  path.normalize + path.sep and rejoining via path.join. Apply the
  same segment-based check to resolveHomeRelativePath and
  resolveOsHomeRelativePath. Drop input.trim() — whitespace is a
  valid filename character on both platforms and env-var inputs are
  already trimmed upstream via normalizeOptionalString.
- coverage-more's "normalizes empty temp names" decomposes the
  result with path.dirname/path.basename instead of regex-matching
  a path-separator literal.
- extracted-helpers' path-helpers test builds its root with
  path.resolve so the drive letter is present on windows.
- additional-boundary-bypass guards its "..\evil.txt" sanitizer
  assertion behind a non-win32 check (windows reserves "\" as a
  path separator and cannot have it in a filename).
- coverage-more's sibling temp test guards just the posix file-mode
  assertion (stat.mode & 0o777 === 0o600), which has no analog on
  windows. The syncing behaviour the test actually targets still
  runs on both platforms.
- Raise test/new-primitives.test.ts size budget to 1500 to
  accommodate the secure-file-reads test split.

After: 253 passed, 1 failed, 66 skipped on windows-11-arm64. The
single remaining failure is a separate library-side gap (a
SAFE_REJECTED_SUSPICIOUS_WRITE_PAYLOADS payload resolves on windows
instead of rejecting) and will be tracked in a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:59:24 -07:00
jesse-merhi
f4a7bb1a65 feat: add safe external output writer 2026-05-07 03:05:05 +01:00
Peter Steinberger
ed5df29ad2
refactor(fs): centralize boundary guard primitives 2026-05-06 21:21:37 +01:00
Peter Steinberger
3c508734af
fix: make prepack portable on windows 2026-05-06 07:20:21 +01:00
Peter Steinberger
bac1e5ac39
docs: include new pages in site nav 2026-05-06 04:20:05 +01:00
Peter Steinberger
cc0757aa82
refactor: split root internals and security test helpers 2026-05-06 03:29:31 +01:00
Peter Steinberger
2f4c4262f0
fix: clear lockfile-only during source prepack 2026-05-06 00:09:25 +01:00
Peter Steinberger
b2e6db91c5
fix: make git source prepack self-contained 2026-05-06 00:08:28 +01:00
Peter Steinberger
e210a26af2
feat: unify store helpers 2026-05-06 00:07:28 +01:00
Peter Steinberger
ff2e84aaea
feat: add persistent fs-safe python helper 2026-05-05 23:25:07 +01:00
Peter Steinberger
49d11edd45
ci: add benchmarks and coverage gates 2026-05-05 19:50:19 +01:00
Peter Steinberger
7f2b962c92
docs: refresh for renamed APIs and new stores
Sync the docs site with recent source changes:

- json: writeJsonAtomic/writeTextAtomic → writeJson/writeText; readJsonFile/Strict
  → readJson + readJsonIfExists + tryReadJson; ensureDirMode → dirMode;
  appendTrailingNewline → trailingNewline.
- temp: createPrivateTempWorkspace/createTempFileTarget → tempWorkspace/tempFile;
  PrivateTempWorkspaceOptions → TempWorkspaceOptions; TempFileTarget → TempFile.
- atomic: replaceDirectoryStaged → replaceDirectoryAtomic; mode/dirMode names.
- root: read{Path → Absolute}; RootDefaults gains mode; per-method options pick
  mode through RootWriteOptions / RootCopyOptions / RootOpenWritableOptions.
- secret-file: writePrivateSecretFileAtomic gains mode/dirMode and now requires
  rootDir; documents the asserted-mode behavior on each component.
- sidecar-lock: SidecarLockHandle has [Symbol.asyncDispose]; document the new
  top-level withSidecarLock helper.

New pages:
- file-store.md — fileStore() + copyIntoRoot.
- json-store.md — jsonStore<T>() with optional cross-process lock.

Add a "Stores" section to the sidebar; update install.md subpath table to list
json-store, file-store, and local-roots.
2026-05-05 19:30:54 +01:00
Peter Steinberger
f0d61765f5
docs: add social card for fs-safe.io
1200×630 PNG (rendered from SVG) wired into og:image / twitter:image meta
on every page. Dark theme matches the site, shield mark + wordmark + tagline
+ url. Twitter card upgraded to summary_large_image.
2026-05-05 19:05:10 +01:00
Peter Steinberger
a559283ac5
docs: add fs-safe.io documentation site
- 25 markdown guides covering every public primitive: root(), pathScope(),
  reading/writing, atomic writes, JSON, temp workspaces, archive extraction,
  secret files, sidecar locks, pinned open, local roots, path/filename helpers,
  install paths, errors, types, testing, timing, contributing.
- Custom Node-based static site generator (scripts/build-docs-site.mjs) with
  link validation, dark mode, mobile nav, search, copy buttons.
- GitHub Actions workflow deploys to Pages on docs/script changes.
- CNAME points to fs-safe.io (DNS already configured to GitHub Pages IPs).

To activate: switch repo Pages source from legacy/branch to workflow.
2026-05-05 17:57:31 +01:00