Skip to content

Add Os::SandboxedFile and Os::FilePathUtils for directory-sandboxed file access - #5320

Merged
LeStarch merged 20 commits into
nasa:develfrom
JPL-Devin:devin/1781106608-sandboxed-file
Jul 9, 2026
Merged

LeStarch merged 20 commits into
nasa:develfrom
JPL-Devin:devin/1781106608-sandboxed-file

Conversation

@lestarch-autobot

@lestarch-autobot lestarch-autobot commented Jun 23, 2026 •

Copy link
Copy Markdown
Collaborator
Related Issue(s) N/A — new feature
Has Unit Tests (y/n) y (21 tests: 14 FilePathUtils + 7 SandboxedFile)
Documentation Included (y/n) y (Doxygen comments on all public APIs)
Generative AI was used in this contribution (y/n) AI

Change Description

Adds two new Os-layer utilities for directory-sandboxed file access:

  • Os::FilePathUtils — standalone namespace for textual path resolution and containment checking:

    • resolvePath(path, baseDir, out, size) — resolves ., .., // in-place via single-pass read/write pointer algorithm. No heap, no I/O.
    • resolveFromCwd(path, out, size) — convenience wrapper resolving relative paths against CWD.
    • checkContainment(resolvedPath, allowedDir) — prefix-based containment check on already-resolved paths.
  • Os::SandboxedFile — composition wrapper around Os::File:

    • configure(dir) resolves the allowed directory at configuration time (relative paths resolved against CWD). Asserts on failure. Trailing / appended automatically.
    • open(path, ...) resolves file paths against CWD, checks containment, then delegates to Os::File::open().
    • Defaults to "/" (all paths allowed) for backward compatibility.

Svc::FileUplink updated as opt-in example with configureSandbox() method.

Rationale

Multiple F Prime components (FileUplink, CFDP receiver) need to restrict file access to approved directories. Without sandboxing, an uplinked file packet with a path like ../../etc/shadow could write anywhere on the filesystem. This provides a reusable, flight-grade mechanism — any component can adopt it by swapping Os::File → Os::SandboxedFile.

Testing/Review Recommendations

  • Os_FilePathUtils_Test: 14 tests covering path resolution edge cases (., .., //, buffer overflow, empty segments, root traversal, relative paths, StringBase overloads)
  • Os_SandboxedFile_Test: 7 tests covering sandbox enforcement, configure/reconfigure, default behavior, traversal rejection
  • All existing FileUplink UTs pass unchanged
  • Path resolution is purely textual — does NOT resolve symlinks (documented; future work for POSIX targets)

Future Work

  • Optional realpath() integration for symlink resolution on POSIX targets
  • CFDP receiver integration (same pattern as FileUplink)

AI Usage (see policy)

This PR was authored by Devin (AI software engineer by Cognition AI) with iterative code review and design direction from @LeStarch. AI was used for: code generation, test writing, documentation, and addressing review feedback across multiple review rounds.

IAMAI

Comment thread Os/FilePathUtils.cpp

// Helper: copy the raw (unresolved) absolute path into resolvedOut.
// If path is relative, it is prepended with baseDir.
static Status buildAbsolutePath(const char* path, const char* baseDir, char* resolvedOut, FwSizeType resolvedSize) {
Comment thread Os/FilePathUtils.cpp
// - Empty or `.` segments are skipped (no-op)
// - `..` segments back up writePos to the previous '/'
// - Normal segments are shifted left (via memmove) to writePos
static Status resolveInPlace(char* resolvedOut) {
Comment thread Os/FilePathUtils.cpp
return VALID;
}

Status resolvePath(const char* path, const char* baseDir, char* resolvedOut, FwSizeType resolvedSize) {
Comment thread Os/FilePathUtils.cpp
// Internal containment check on already-resolved paths.
// Verifies that resolvedPath starts with allowedDirectory as a prefix,
// and that the match occurs at a `/` boundary.
Status checkContainment(const char* resolvedPath, const char* allowedDirectory) {
Comment thread Os/FilePathUtils.cpp Fixed
Comment thread Os/SandboxedFile.cpp

namespace Os {

SandboxedFile::SandboxedFile() : m_file(), m_allowedDirectory("/"), m_configured(true) {}
Comment thread Os/SandboxedFile.cpp
}
}

void SandboxedFile::configure(const char* allowedDirectory) {
Comment thread Os/SandboxedFile.cpp
return m_configured;
}

Os::FileInterface::Status SandboxedFile::open(const char* path,

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@github-actions

github-actions Bot commented Jun 23, 2026 •

Copy link
Copy Markdown
Contributor

Coverage report — base devel

Overall (line): 82.01% → 82.07% (+0.06)
Regression threshold: 0.50% (line).

Regressions

Module Line Δ Function Δ Branch Δ
Svc/FprimeDeframer 98.15 -1.85 100.00 +0.00 96.00 -4.00

Modules changed

Module Line Δ Function Δ Branch Δ
Svc/FprimeDeframer 98.15 -1.85 100.00 +0.00 96.00 -4.00
Os/Posix 62.00 -0.40 84.21 +0.00 43.87 -0.23
Svc/FileUplink 92.46 +0.11 96.67 +0.12 80.95 +0.00
Os 21.23 +6.40 23.30 +5.66 18.96 +7.31

Modules without UTs

CFDP/Checksum/GTest, Drv/ByteStreamDriverModel, Drv/Interfaces, Drv/LinuxGpioDriver, Drv/LinuxI2cDriver, Drv/LinuxSpiDriver, Drv/LinuxUartDriver, Drv/Ports, Drv/Ports/DataTypes, FppTestProject/FppTest/interfaces, FppTestProject/FppTest/topology/async, FppTestProject/FppTest/topology/components/Comp, FppTestProject/FppTest/topology/components/Framework, FppTestProject/FppTest/topology/components/Receiver, FppTestProject/FppTest/topology/components/Sender, FppTestProject/FppTest/topology/guarded, FppTestProject/FppTest/topology/ports, FppTestProject/FppTest/topology/sync, FppTestProject/FppTest/topology/top_ports, FppTestProject/FppTest/topology/types, Fw/Cmd, Fw/Com, Fw/Comp, Fw/FilePacket/GTest, Fw/Fpy, Fw/Interfaces, Fw/Obj, Fw/Port, Fw/Ports/CompletionStatus, Fw/Ports/Ready, Fw/Ports/Signal, Fw/Ports/SuccessCondition, Fw/Prm, Fw/SerializableFile/test/TestSerializable, Fw/Sm, Fw/Test, Fw/Types/GTest, Os/Models, Svc/Cycle, Svc/DpPorts, Svc/Fatal, Svc/FatalHandler, Svc/FileDownlinkPorts, Svc/FprimeProtocol, Svc/Interfaces, Svc/PassiveConsoleTextLogger, Svc/Ping, Svc/PolyIf, Svc/Ports/CommsPorts, Svc/Ports/FilePorts, Svc/Ports/OsTimeEpoch, Svc/Ports/TlmPacketizerPorts, Svc/Ports/VersionPorts, Svc/Sched, Svc/Seq, Svc/Subtopologies/CdhCore, Svc/Subtopologies/ComCcsds, Svc/Subtopologies/ComFprime, Svc/Subtopologies/ComLoggerTee, Svc/Subtopologies/DataProducts, Svc/Subtopologies/DpCompression, Svc/Subtopologies/FileHandling, Svc/Types/TlmPacketizerTypes, Svc/WatchDog, TestDeploymentsProject/Ref/PingReceiver, TestDeploymentsProject/Ref/RecvBuffApp, TestDeploymentsProject/Ref/SendBuffApp, TestDeploymentsProject/Ref/Top, TestDeploymentsProject/Ref/TypeDemo, cmake/test/data/TestDeployment/TestBuildAutocoder, cmake/test/data/TestDeployment/TestChainedAutocoder, cmake/test/data/TestDeployment/TestHeaderAutocoder, cmake/test/data/TestDeployment/TestTargetAutocoder, cmake/test/data/test-fprime-library/TestLibrary/TestComponent, cmake/test/data/test-fprime-library2/TestLibrary2/TestComponent

Comment thread Os/SandboxedFile.cpp Resolved
LeStarch
LeStarch previously approved these changes Jun 23, 2026
Comment thread Os/FilePathUtils.cpp Dismissed
Comment thread Os/FilePathUtils.cpp

// Helper: copy the raw (unresolved) absolute path into resolvedOut.
// If path is relative, it is prepended with baseDir.
static Status buildAbsolutePath(const char* path, const char* baseDir, char* resolvedOut, FwSizeType resolvedSize) {
Comment thread Os/FilePathUtils.cpp

// Helper: copy the raw (unresolved) absolute path into resolvedOut.
// If path is relative, it is prepended with baseDir.
static Status buildAbsolutePath(const char* path, const char* baseDir, char* resolvedOut, FwSizeType resolvedSize) {
Comment thread Os/FilePathUtils.cpp
// - Empty or `.` segments are skipped (no-op)
// - `..` segments back up writePos to the previous '/'
// - Normal segments are shifted left (via memmove) to writePos
static Status resolveInPlace(char* resolvedOut) {
Comment thread Os/FilePathUtils.cpp
return VALID;
}

Status resolvePath(const char* path, const char* baseDir, char* resolvedOut, FwSizeType resolvedSize) {
Comment thread Os/SandboxedFile.cpp Dismissed
Comment thread Os/SandboxedFile.cpp Dismissed
Comment thread Os/SandboxedFile.cpp Dismissed
Comment thread Os/SandboxedFile.cpp Dismissed
Comment thread Svc/FileUplink/FileUplink.cpp Dismissed
Comment thread Os/FilePathUtils.cpp Dismissed
Comment thread Os/SandboxedFile.hpp Outdated
//!
//! Note: path validation is purely textual (no `realpath()` calls) — it does not follow
//! symlinks. For deployments where symlink-based escapes are a concern, additional
//! measures are needed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Security] could fix general-vulnerability/symlink-toctou: Textual path validation does not follow symlinks — an attacker who can create symlinks inside the sandbox directory could escape containment.

The PR documents this limitation (line 26: "does not follow symlinks") and lists realpath() integration as future work. On embedded targets without symlink support this is a non-issue; on POSIX targets with untrusted local access it becomes exploitable. Consider documenting the threat model assumption (no symlink creation by untrusted actors) explicitly in the class-level doc comment.

Comment thread Os/FilePathUtils.cpp
Status resolvePath(const Fw::ConstStringBase& path, const Fw::ConstStringBase& baseDir, Fw::StringBase& resolvedOut) {
// SAFETY: write directly into resolvedOut's internal buffer to avoid a temporary copy.
// resolvePath always null-terminates the output, keeping the StringBase in a valid state.
char* outBuffer = const_cast<char*>(resolvedOut.toChar());

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[C++ Design] could fix CPP-10 (const_cast without full justification): const_cast<char*>(resolvedOut.toChar()) writes directly into a StringBase's internal buffer, bypassing the class's encapsulation.

The SAFETY comment explains the motivation (avoid a temporary buffer copy), but this pattern couples the implementation to the assumption that toChar() returns a writable buffer whose lifetime and capacity match the StringBase's internal storage. If StringBase ever changes its storage model, this will silently corrupt. Consider using a local char[] buffer and then assigning to resolvedOut via format/copy.

Comment thread Os/SandboxedFile.hpp
//! // -> NO_PERMISSION
//! ```
//!
class SandboxedFile {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[C++ Design] must fix CPP-17 (rule of three/five): SandboxedFile has a user-defined destructor (line 47) that closes the file, but does not declare or = delete copy/move constructors or assignment operators.

Since Os::File is likely non-copyable, the implicit copy operations are probably ill-formed, but they should be explicitly deleted to prevent accidental misuse and to satisfy the rule-of-three/five invariant.

Suggested change
class SandboxedFile {
class SandboxedFile {
public:
SandboxedFile(const SandboxedFile&) = delete;
SandboxedFile& operator=(const SandboxedFile&) = delete;

Comment thread Os/FilePathUtils.hpp
//! and verifying that a resolved path falls within an allowed directory prefix.
//! These utilities can be used independently of SandboxedFile for any path-validation need.
//!
namespace FilePathUtils {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Documentation] must fix stale-sdd: Os/docs/sdd.md does not mention the new FilePathUtils or SandboxedFile utilities.

The Os SDD's section 2.2 "File system" table lists File, Directory, and FileSystem but does not include the two new utilities this PR adds. Since these are public APIs in the Os layer, the SDD should be updated with entries for both. A row for each in the §2.2 table (or a new §3 "Derived Services" subsection) would satisfy this.

//!
//! \param directory: absolute path of the allowed uplink directory (must end with `/`)
//!
void configure(const char* directory);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Documentation] suggestion stale-sdd: Svc/FileUplink/docs/sdd.md does not mention the new configure() method or sandbox behavior.

The PR adds a public configure(const char* directory) method that restricts file writes to a sandbox directory. The SDD's §3 "Design" section should note this opt-in security feature and its default behavior (sandbox = /, all paths allowed). A one-line addition to §3.1 "Assumptions" would suffice.

Comment thread Os/SandboxedFile.hpp

namespace Os {

//! \brief A wrapper around Os::File that restricts file access to an allowed directory

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Documentation] suggestion missing-doc-for-new-surface: New Os::SandboxedFile is a reusable security mechanism but has no how-to guide or user-manual entry.

The Doxygen comments are thorough. However, since this utility is intended for adoption by multiple components (FileUplink, CFDP receiver, etc.), a brief entry in docs/how-to/ (e.g., "sandboxing file access") would help downstream users discover and adopt it.

Comment thread Os/SandboxedFile.cpp
char resolved[FilePathUtils::MAX_PATH_LENGTH];
const FilePathUtils::Status resolveStatus =
FilePathUtils::resolveFromCwd(allowedDirectory, resolved, sizeof(resolved));
FW_ASSERT(resolveStatus == FilePathUtils::VALID);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Design] could fix design-fault-handling-gap: configure() uses FW_ASSERT for error handling (lines 21, 22, 28, 32-33) instead of returning a status code.

The doc comment notes "Asserts on failure" and "Intended to be called once at startup," which justifies asserts for programming errors (nullptr, file-already-open). However, the assert on resolveStatus == VALID (line 28) could fail due to an environment issue (CWD unavailable) rather than a programming error. Returning a status would let the caller handle this gracefully.

ASSERT_EQ(Os::FilePathUtils::VALID, status);
ASSERT_STREQ("/data/uplink/file.bin", resolved.toChar());
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Test Quality] must fix test-coverage-missing-new-fpp: checkContainment() is a public API in Os::FilePathUtils but has no dedicated unit tests.

The function is tested indirectly through SandboxedFileTest, but as a public API it should have its own test cases covering: exact prefix match, prefix sharing a common start but diverging at a non-/ boundary (e.g., /data/upload vs /data/uploadevil/), root sandbox (/), empty strings, and the special case where path equals allowed directory minus trailing /.

auto status = Os::FilePathUtils::resolvePath(path, baseDir, resolved);
ASSERT_EQ(Os::FilePathUtils::VALID, status);
ASSERT_STREQ("/data/uplink/file.bin", resolved.toChar());
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Test Quality] suggestion test-coverage-missing-new-fpp: resolveFromCwd() is a public API but has no dedicated unit tests.

It's tested indirectly through SandboxedFile, but a direct test would verify behavior for both relative and absolute inputs without the sandbox layer.

Suggested change
}
TEST_F(FilePathUtilsResolveTest, ResolveFromCwdAbsolute) {
char resolved[Os::FilePathUtils::MAX_PATH_LENGTH];
auto status = Os::FilePathUtils::resolveFromCwd("/data/file.bin", resolved, sizeof(resolved));
ASSERT_EQ(Os::FilePathUtils::VALID, status);
ASSERT_STREQ("/data/file.bin", resolved);
}

TEST_F(SandboxedFileTest, GetSandboxDirectoryDefault) {
Os::SandboxedFile file;
ASSERT_STREQ("/", file.getSandboxDirectory());
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Test Quality] suggestion test-fail-path-missing: No test for SandboxedFile::open() with an empty path or a path exceeding MAX_PATH_LENGTH.

These failure paths exercise the error-handling code in resolveFromCwd and resolvePath. A test confirming that open() returns NO_PERMISSION (not an assert crash) on malformed input would strengthen the safety contract.

@lestarch-autobot lestarch-autobot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Automated review summary (run 1)

Per-agent results

Agent must fix suggestion could fix future work outstanding Verdict
Security Vulnerabilities 0 0 1 0 1 Go
Supply Chain / Runner Safety 0 0 0 0 0 Go
F Prime C/C++ Design 1 1 1 0 3 No-Go
Documentation Currency 1 2 0 0 3 No-Go
Design 0 0 1 0 1 Go
Architecture 0 0 0 0 0 Go
Test Quality 1 2 0 0 3 No-Go
CI safety — — — — — Go
Totals 3 5 3 0 11 No-Go
Supply-chain surfaces
Surface Outstanding
Dependencies clean
Vendored / submodule clean
Build / test infrastructure clean
Workflows / actions / scripts clean
Generator output clean
Prompt-injection clean
Review-system integrity clean
Outstanding must-fix items (3)

F Prime C/C++ Design

  • CPP-17: SandboxedFile missing explicit = delete for copy/move operations — Os/SandboxedFile.hpp

Documentation Currency

  • Os SDD (Os/docs/sdd.md) missing entries for new FilePathUtils and SandboxedFile utilities — Os/FilePathUtils.hpp

Test Quality

  • checkContainment() public API has no dedicated unit tests — Os/test/ut/FilePathUtilsTest.cpp

Merge readiness

Merge readiness: No-Go — 3 outstanding must-fix items across C++ Design (1), Documentation Currency (1), and Test Quality (1).

Agents that did not run on this PR

  • (none — all 7 reviewers invoked successfully)

Well-structured PR with thorough path-resolution logic and solid test coverage. The three must-fix items are straightforward to address: add explicit copy/move deletion, update the Os SDD, and add dedicated checkContainment tests.

@thomas-bc thomas-bc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

A few recommendations

Comment thread Os/SandboxedFile.cpp Resolved
Comment thread Svc/FileUplink/docs/sdd.md Resolved
LeStarch
LeStarch previously approved these changes Jul 7, 2026

@LeStarch LeStarch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks reasonable to me!

devin-ai-integration Bot and others added 10 commits July 7, 2026 02:01
…ed file access

Introduces a reusable composition wrapper around Os::File that restricts
file operations to a configured allowed directory. This prevents path
traversal attacks from components that accept externally supplied file paths.

New components:
- Os::FilePathValidator: standalone utility for path resolution (collapsing
  . and .. segments) and directory containment checking
- Os::SandboxedFile: wrapper that validates paths on open() before delegating
  to Os::File, returning NO_PERMISSION for paths outside the sandbox

Updates FileUplink to use Os::SandboxedFile with a configureSandbox() method,
demonstrating the opt-in pattern for existing components.

Includes 35 unit tests covering path resolution, containment checks, traversal
attacks, and sandboxed file I/O.

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
Address Devin Review feedback:
- SandboxedFile::open() now delegates to Os::File when unconfigured,
  so existing deployments are not broken by the swap to SandboxedFile.
  Sandboxing activates only after configure() is called.
- Use FW_ASSERT on getcwd() return value in FileUplinkTester to avoid
  undefined behavior if getcwd fails.

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
… FW_ASSERT in configure, require configure before open, use Fw::FileNameString, split tests, use for loops

Changes per review:
- Rename FilePathValidator -> FilePathUtils (comment 27)
- Rename checkContainment -> isSubDirectory with internal resolve (comment 28)
- Rename configureSandbox -> configure in FileUplink (comment 23)
- Use Fw::FileNameString for m_allowedDirectory storage (comment 24)
- Replace if-checks with FW_ASSERT in configure() (comment 25)
- Require configure() before open(), return NO_PERMISSION if unconfigured (comment 26)
- Split tests into FilePathUtilsTest.cpp and SandboxedFileTest.cpp (comment 29)
- Use for loops instead of while loops (comment 30)
- Refactor resolvePath into smaller helpers (buildWorkBuf, resolveSegments)

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
Replace two-buffer approach (workBuf + PathSegment array + reconstruction)
with in-place resolution using read/write pointers on resolvedOut.
The write pointer never overtakes the read pointer (resolution only
shrinks), so memmove is safe. Eliminates ~120-entry PathSegment stack
array and the separate workBuf.

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
…, validation hardening

- Resolve relative paths against CWD (Os::FileSystem::getWorkingDirectory)
  instead of allowed directory in isSubDirectory
- Add Fw::StringBase overloads for resolvePath and isSubDirectory
- Validate string_length != buffer_length (reject unterminated strings)
- Add TOO_LONG status for buffer overflow (distinct from INVALID_PATH)
- Use descriptive variable names (readPos/writePos instead of r/w)
- Add explanatory comments in resolveInPlace algorithm
- Use parentheses around arithmetic in buffer size check
- Verify baseDir length > 0 before indexing
- Remove resolvedOut parameter from isSubDirectory (callers resolve separately)
- Clarify checkContainment edge case where path == allowedDir sans trailing /

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
devin-ai-integration Bot and others added 9 commits July 7, 2026 02:01
…tead of nullptr, scope cwdBuffer

- resolvePath(StringBase): write directly into resolvedOut's internal buffer
  via const_cast (safe because resolvePath always null-terminates)
- isSubDirectory: pass "/" instead of nullptr for allowedDirectory normalization
- Move cwdBuffer allocation inside the if block (only when path is relative)

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
… SandboxedFile::open

- Make checkContainment a public function in FilePathUtils namespace
- SandboxedFile::open now calls resolvePath + checkContainment directly
  instead of going through isSubDirectory (which would resolve again internally)
- Saves one MAX_PATH_LENGTH stack buffer and one redundant resolve pass

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
Prevents mismatch where getCapacity() (MAX_PATH_LENGTH+1) could let
buildAbsolutePath accept a path that resolveInPlace then rejects
because its string_length scan is bounded to MAX_PATH_LENGTH.

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
…pen paths against CWD

- configure() now resolves allowedDirectory (relative paths use CWD as base)
  and asserts resolution succeeds. Trailing '/' appended automatically.
- open() resolves file paths against CWD instead of the sandbox directory,
  matching standard POSIX semantics for relative paths.
- Removes requirement that allowedDirectory be absolute — relative dirs
  are resolved at configure time.

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
…ttern

- Add FilePathUtils::resolveFromCwd() that handles the CWD lookup
  for relative paths in a single call
- Refactor isSubDirectory, SandboxedFile::configure, and
  SandboxedFile::open to use resolveFromCwd instead of duplicating
  the CWD-fetch-then-resolve logic

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
isSubDirectory resolved paths internally and checked containment. Now
that SandboxedFile::open uses resolveFromCwd + checkContainment directly,
isSubDirectory has no callers. Removed both overloads and all 15 tests.

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
…emoval

- Delete copy/move constructors on SandboxedFile (rule of three/five)
- Add 8 dedicated checkContainment() unit tests covering prefix boundaries,
  root sandbox, empty strings, and subdirectory containment
- Add 2 resolveFromCwd() unit tests (absolute passthrough, relative resolution)
- Add 2 SandboxedFile edge-case tests (empty path, overlong path)
- Replace const_cast in StringBase overload with local buffer + assignment
- Replace magic 256 with MAX_PATH_LENGTH in FileUplinkTester
- Update Os SDD to include FilePathUtils and SandboxedFile entries
- Update FileUplink SDD to document sandbox configure() behavior
- Document symlink threat model assumption in SandboxedFile class comment

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
- Add Os::FileInterface::OUTSIDE_SANDBOX status to distinguish sandbox
  rejection from generic NO_PERMISSION errors. Update both the C++ enum
  and the FPP shadow enum (FileStatus) with static_assert consistency.

- SandboxedFile::open now returns OUTSIDE_SANDBOX instead of
  NO_PERMISSION when a path fails containment or resolution checks.

- Configure FileUplink sandbox in Ref deployment via FPP configComponents
  phase: FileHandling::fileUplink.configure("/tmp/uplink/").

- Add test_file_uplink_sandbox integration test in Ref that verifies:
  1. Uplink to /tmp/evil/escaped.bin (outside sandbox) -> FileOpenError
  2. Uplink to /tmp/uplink/test.bin (inside sandbox) -> FileReceived

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
@devin-ai-integration
devin-ai-integration Bot force-pushed the devin/1781106608-sandboxed-file branch from 5c996bf to 72d4a85 Compare July 7, 2026 02:02
- Per review, move FileUplink sandbox configuration out of the shared
  FileHandling subtopology (which would force it on all deployments) and
  into Ref's RefTopology.cpp configureTopology(), leaving the sandbox
  directory choice up to each deployment.

- Fix clang-format include ordering in FilePathUtilsTest.cpp and
  FileUplinkTester.cpp.

- Fix black formatting in ref_integration_test.py.

Co-Authored-By: michael.d.starch <michael.d.starch@jpl.nasa.gov>
@LeStarch LeStarch closed this Jul 9, 2026
@LeStarch LeStarch reopened this Jul 9, 2026
@LeStarch
LeStarch merged commit 4c8b9c0 into nasa:devel Jul 9, 2026
133 of 177 checks passed
github-actions Bot added a commit that referenced this pull request Jul 9, 2026
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.

4 participants