ndaal Informational Advisory: capability-based filesystem hardening — cap-std / cap-tempfile workspace declaration, `vl_core::path_security` primitive promotion, and 57-vector exhaustive path-traversal regression suite

Edit JSON

Metadata

Tracking IDndaal-sa-2026-045
Categorycsaf_informational_advisory
CSAF Version2.1
Statusfinal
Version1.0.0
Publisherndaal Gesellschaft fuer Sicherheit in der Informationstechnik mbH & Co KG
TLPCLEAR
Initial Release2026-05-16T13:41:44.000Z
Current Release2026-05-16T13:41:44.000Z

Vulnerabilities (0)

No vulnerabilities.

Raw JSON

{
  "$schema": "https://docs.oasis-open.org/csaf/csaf/v2.1/schema/csaf.json",
  "document": {
    "category": "csaf_informational_advisory",
    "csaf_version": "2.1",
    "distribution": {
      "tlp": {
        "label": "CLEAR"
      }
    },
    "lang": "en",
    "notes": [
      {
        "category": "summary",
        "text": "nvulnlookup v0.1.44 commit chain (4a271f314 + f9ae5a647) adds capability-based filesystem hardening: the Bytecode Alliance `cap-std` / `cap-tempfile` crates are declared as workspace dependencies with a 27-line security rationale, the existing path-sanitisation primitive is promoted from a fuzz-target-local function to the public `vl_core::path_security::safe_join` utility, and a 57-vector exhaustive path-traversal regression suite (ported from four threat corpora) is added at `crates/vl-core/tests/test_path_security.rs`.  Existing fuzz target `fuzz_advisory_path_traversal` is consolidated to import the promoted primitive, so the unit-test corpus and the fuzz corpus now exercise the same function.  This is preparatory hardening — the cap-std crates are declared but not yet wired into individual feeders; the syscall-level `openat2(RESOLVE_BENEATH)` boundary lands in a follow-up release.",
        "title": "Summary"
      },
      {
        "category": "description",
        "text": "Three layers of defence-in-depth land in this commit chain.\n\n1. Workspace dependency declaration (commit 4a271f314):\n\n   The `[workspace.dependencies]` block in `vulnerability-lookup-rs/Cargo.toml` now declares:\n\n     cap-std      = { version = \"3\" }\n     cap-tempfile = { version = \"3\" }\n\n   Both crates come from the Bytecode Alliance.  `cap-std` exposes a `Dir` handle that scopes every subsequent `read_to_string` / `write` / `create_dir_all` / `read_dir` to a pre-opened directory; paths are interpreted RELATIVE to that Dir handle and any traversal that would escape the handle's subtree (`../`, absolute paths, symlinks pointing outside) is rejected at the syscall layer via `openat2(RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS | RESOLVE_NO_MAGICLINKS)` on Linux >= 5.6 and explicit component-by-component resolution on older platforms.  `cap-tempfile` is the capability-secure equivalent of `tempfile::TempDir`; its temp directory is created mode 0700 and cleaned up via the open fd, immune to a parent rename mid-cleanup.\n\n   The declaration is a no-op at runtime — no member crate yet opts in via `{ workspace = true }`.  Subsequent commits will create the `vl_core::fs::DataDir` central wrapper and migrate `std::fs` / `walkdir` call sites feeder-by-feeder.  Documented end-to-end in `skills/rust-path-security.md` (six BAD/GOOD example pairs, the tokio `spawn_blocking` integration, and the std::fs -> cap-std translation table).\n\n2. Path-security primitive promoted to vl-core (commit f9ae5a647):\n\n   `safe_join(dest_dir: &Path, path: &str) -> Option<PathBuf>` now lives at `crates/vl-core/src/path_security.rs` as a public utility.  Same behaviour as the previous fuzz-local copy: lexical resolution, rejects absolute paths, rejects NUL bytes, walks `..` components with pop-on-fail, and post-validates that the result starts with `dest_dir` and contains no residual `..`.  Feeders that construct destination paths from caller-supplied input (CSAF `tracking_id`, RSS / Atom entry URLs, archive entry names, CLI `--output-dir` overrides) can now adopt the workspace-wide primitive instead of inlining ad-hoc `Component::ParentDir` checks.\n\n3. Exhaustive path-traversal test suite (commit f9ae5a647):\n\n   New file `crates/vl-core/tests/test_path_security.rs` contributes 57 vectors drawn from four threat corpora, each tagged in the test name so a regression points at the original threat:\n\n     - cs_*      — cap-std upstream test files (`bytecodealliance/cap-std`): `tests/symlinks.rs`, `tests/paths-containing-nul.rs`, `tests/canonicalize.rs`, `tests/fs.rs`, `tests/fs_additional.rs`, `tests/root.rs`.  Same threat model the cap-std migration will enforce at the syscall layer, ported to the string layer.\n     - owasp_*   — OWASP \"Path Traversal\" cheat sheet (CWE-22): absolute-path injection, URL-encoded `../`, double-encoded variants, NUL-byte truncation (CVE-2007-0883 class), and the Windows / UNC / drive-letter shapes that frequently slip past naive `starts_with` checks.\n     - csaf_*    — the actual attacker-controllable string fields in our CSAF / OSV / RSS feeders (`tracking_id`, advisory path from `changes.csv`, Atom entry `<id>` URI fragments).  Includes a 32-level deep traversal vector to defend against patterns that try to overflow a usize depth counter.\n     - unicode_* — combining marks (NFC vs NFD), RTL override, fullwidth full stop, zero-width joiner, homoglyph slash — cases that have produced sandbox escapes in other projects (Node.js path-traversal advisories, Apache Tomcat CVE-2014-0050 family).\n\n   Property-style coverage rounds out the suite: 1..=64 traversal depths must all reject; balanced pop-then-descend (n in 1..=16) must all reject; balanced pop alone (no trailing `..`) must all accept; NUL injection at every byte position of a benign filename must reject; idempotence + post-condition invariants cross-check the assertions the fuzz target relies on.\n\n   Fuzz target `crates/vl-core/fuzz/fuzz_targets/fuzz_advisory_path_traversal.rs` is consolidated to import `vl_core::path_security::safe_join` (was a 47-line local copy; now 13 lines including imports).  The unit-test corpus and the fuzz corpus now drive the same function so any regression fails both gates simultaneously rather than just one.\n\nIncidental: this commit chain also exempts `sha3:0.10.9` in `supply-chain/config.toml` (a transitive version bump from 0.10.8 inherited via `vl-feeders`' gsd archive verification) to unblock the `cargo vet check` gate.  Routine version-bump exemption, same audit posture as the rest of the workspace's hashing-crate entries.\n\nVerification:\n  cargo check -p vl-core                                        OK\n  cargo clippy -p vl-core --tests -- -D warnings                OK\n  cargo test -p vl-core --test test_path_security               57/57 PASS\n  cargo vet check                                                Succeeded\n                                                                 (211 fully audited,\n                                                                  6 partially audited,\n                                                                  269 exempted)\n\nNot a security vulnerability under CVSS — no CIA impact (defensive hardening only, no behaviour change in shipped feeders yet); severity informational.",
        "title": "Description"
      },
      {
        "category": "details",
        "text": "Commit chain on origin/claude/vigilant-proskuriakova-981064:\n\n  4a271f314  deps(workspace): declare cap-std + cap-tempfile workspace dependencies\n  f9ae5a647  feat(vl-core): path_security module + exhaustive traversal regression suite\n\nKey files added / modified:\n\n  vulnerability-lookup-rs/Cargo.toml                                  +27 lines  (cap-std + cap-tempfile + 27-line security rationale)\n  vulnerability-lookup-rs/crates/vl-core/src/path_security.rs         NEW, +90 lines  (public safe_join utility + threat-model docstring)\n  vulnerability-lookup-rs/crates/vl-core/src/lib.rs                   +1 line  (pub mod path_security;)\n  vulnerability-lookup-rs/crates/vl-core/tests/test_path_security.rs  NEW, +565 lines  (57 test vectors + 6 property tests + post-condition + idempotence)\n  vulnerability-lookup-rs/crates/vl-core/fuzz/fuzz_targets/fuzz_advisory_path_traversal.rs   -47 / +13 lines  (consolidation: import promoted primitive)\n  vulnerability-lookup-rs/supply-chain/config.toml                    +4 lines  (sha3:0.10.9 exemption)\n\nDocumentation companions (already on main via the earlier merge of claude/hungry-rhodes-5caad8):\n\n  skills/rust-path-security.md      NEW, ~600 lines  (six BAD/GOOD example pairs, DataDir wrapper, tokio spawn_blocking, std::fs -> cap-std table, CWE-22 / CWE-59 / CWE-367 + openat2(2) references)\n  CLAUDE.md                         +entry under \"Related Tools\" and \"Rust specific\"\n  skills/rust.md                    cross-reference next to the existing cap-std mention\n  skills/rust-{cli-builder,container,csaf,hashes,pro,release,sarif,supply-chain,testing,verification}.md   +Related-skills entry pointing at rust-path-security.md\n\nThreat surface covered:\n\n  CWE-22  — Improper Limitation of a Pathname to a Restricted Directory (\"Path Traversal\")\n  CWE-59  — Improper Link Resolution Before File Access (\"Link Following\") — covered at the string layer now, syscall-level cap-std mitigation follows\n  CWE-367 — Time-of-check Time-of-use (TOCTOU) Race Condition — string layer cannot mitigate; documented as deferred-to-cap-std in `vl_core::path_security` docstring\n\nNo runtime / data / behavioural change.  The promoted `safe_join` is not yet wired into any feeder; the cap-std crates are declared but not yet imported anywhere.  Operators do not need to take any action.",
        "title": "Commits, files, and threat coverage"
      },
      {
        "category": "general",
        "text": "Recommended action for downstream consumers: none.  This is a documentation + test-coverage + supply-chain-declaration refresh.  No feeder behaviour changes; no API changes; no Cargo.lock changes (the new workspace deps are declared but not yet consumed by any member crate, so they do not enter the resolved dep tree).\n\nFor contributors planning new feeders, route handlers, or any code that constructs a destination path from caller-supplied input:\n\n  1. Use `vl_core::path_security::safe_join(base, rel)` instead of `base.join(rel)`.  Returns `None` on traversal / NUL / absolute injection; emit a `tracing::warn!` and skip the entry on `None`.\n  2. When the cap-std migration lands (target: v0.1.45 release branch), the constructor signature for every feeder will change from `base_dir: &Path` to `base_dir: &cap_std::fs::Dir` or `&vl_core::fs::DataDir`.  Plan the constructor signature accordingly.\n  3. Read `skills/rust-path-security.md` for the six BAD/GOOD example pairs and the std::fs -> cap-std translation table.  The skill documents the exact patterns the migration will land.\n\nFor operators integrating nvulnlookup into a larger workspace: no action required.  The cap-std workspace deps are declared at `version = \"3\"` (resolves to cap-std 3.4.5 on crates.io); the next planned bump to `version = \"4\"` (4.0.2 latest) will accompany the first migrated feeder.",
        "title": "Recommended action"
      },
      {
        "category": "other",
        "text": "Disposition: informational — defensive hardening, no security or behavioural impact on shipped binaries.  The promoted `safe_join` primitive and the 57-vector regression suite improve detectability of any future bug in the path-sanitisation contract; the cap-std workspace declaration is the load-bearing first step of the multi-commit migration documented in `skills/rust-path-security.md`.  Status final.",
        "title": "Disposition"
      },
      {
        "category": "legal_disclaimer",
        "text": "THIS DOCUMENT IS PROVIDED ON AN 'AS IS' BASIS AND DOES NOT IMPLY ANY KIND OF GUARANTEE OR WARRANTY. ndaal Gesellschaft fuer Sicherheit in der Informationstechnik mbH & Co KG DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS INFORMATION INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS."
      }
    ],
    "publisher": {
      "category": "vendor",
      "contact_details": "security@ndaal.eu",
      "issuing_authority": "ndaal Gesellschaft fuer Sicherheit in der Informationstechnik mbH & Co KG Security Team",
      "name": "ndaal Gesellschaft fuer Sicherheit in der Informationstechnik mbH & Co KG",
      "namespace": "https://ndaal.eu/csaf"
    },
    "references": [
      {
        "category": "self",
        "summary": "This advisory in CSAF 2.1 format",
        "url": "https://gitlab.com/vPierre/ndaal_public_nvulnlookup/-/raw/main/csaf/2026/045/ndaal-sa-2026-045.json"
      },
      {
        "category": "external",
        "summary": "skills/rust-path-security.md — full cap-std migration plan, six BAD/GOOD pairs, threat model",
        "url": "https://gitlab.com/vPierre/ndaal_public_nvulnlookup/-/raw/main/skills/rust-path-security.md"
      },
      {
        "category": "external",
        "summary": "cap-std crate (Bytecode Alliance)",
        "url": "https://crates.io/crates/cap-std"
      },
      {
        "category": "external",
        "summary": "cap-tempfile crate (Bytecode Alliance)",
        "url": "https://crates.io/crates/cap-tempfile"
      },
      {
        "category": "external",
        "summary": "Bytecode Alliance cap-std repository (upstream tests this suite ports)",
        "url": "https://github.com/bytecodealliance/cap-std"
      },
      {
        "category": "external",
        "summary": "Linux openat2(2) — RESOLVE_BENEATH, RESOLVE_NO_SYMLINKS, RESOLVE_NO_MAGICLINKS",
        "url": "https://man7.org/linux/man-pages/man2/openat2.2.html"
      },
      {
        "category": "external",
        "summary": "CWE-22 — Improper Limitation of a Pathname to a Restricted Directory (Path Traversal)",
        "url": "https://cwe.mitre.org/data/definitions/22.html"
      },
      {
        "category": "external",
        "summary": "CWE-59 — Improper Link Resolution Before File Access (Link Following)",
        "url": "https://cwe.mitre.org/data/definitions/59.html"
      },
      {
        "category": "external",
        "summary": "CWE-367 — Time-of-check Time-of-use (TOCTOU) Race Condition",
        "url": "https://cwe.mitre.org/data/definitions/367.html"
      },
      {
        "category": "external",
        "summary": "OWASP Path Traversal cheat sheet (vector source for owasp_* tests)",
        "url": "https://owasp.org/www-community/attacks/Path_Traversal"
      },
      {
        "category": "external",
        "summary": "GitLab MR !19 (commit chain landing this hardening)",
        "url": "https://gitlab.com/vPierre/ndaal_public_nvulnlookup/-/merge_requests/19"
      },
      {
        "category": "external",
        "summary": "nvulnlookup CHANGELOG",
        "url": "https://gitlab.com/vPierre/ndaal_public_nvulnlookup/-/raw/main/vulnerability-lookup-rs/CHANGELOG.md"
      }
    ],
    "title": "ndaal Informational Advisory: capability-based filesystem hardening — cap-std / cap-tempfile workspace declaration, `vl_core::path_security` primitive promotion, and 57-vector exhaustive path-traversal regression suite",
    "tracking": {
      "current_release_date": "2026-05-16T13:41:44.000Z",
      "generator": {
        "engine": {
          "name": "ndaal CSAF Generator",
          "version": "1.0.0"
        }
      },
      "id": "ndaal-sa-2026-045",
      "initial_release_date": "2026-05-16T13:41:44.000Z",
      "revision_history": [
        {
          "date": "2026-05-16T13:41:44.000Z",
          "number": "1.0.0",
          "summary": "Initial informational advisory documenting the capability-based filesystem hardening commit chain (cap-std / cap-tempfile workspace declaration + path_security module promotion + 57-vector exhaustive path-traversal regression suite + fuzz-target consolidation + sha3:0.10.9 vet exemption to unblock the merge gate).  Status: final."
        }
      ],
      "status": "final",
      "version": "1.0.0"
    }
  },
  "product_tree": {
    "branches": [
      {
        "category": "vendor",
        "name": "ndaal Gesellschaft fuer Sicherheit in der Informationstechnik mbH & Co KG",
        "branches": [
          {
            "category": "product_name",
            "name": "nvulnlookup",
            "branches": [
              {
                "category": "product_version",
                "name": "0.1.44-pre-cap-std",
                "product": {
                  "name": "nvulnlookup 0.1.44 prior to the cap-std hardening commit chain — `safe_join` was a fuzz-target-local function, no cap-std workspace declaration, no exhaustive path-traversal regression suite",
                  "product_id": "CSAFPID-0001"
                }
              },
              {
                "category": "product_version",
                "name": "0.1.44-post-cap-std",
                "product": {
                  "name": "nvulnlookup 0.1.44 with commits 4a271f314 + f9ae5a647 — `vl_core::path_security::safe_join` is a public workspace primitive, cap-std + cap-tempfile declared in `[workspace.dependencies]` with security rationale, 57-vector exhaustive path-traversal regression suite at `crates/vl-core/tests/test_path_security.rs`, fuzz target `fuzz_advisory_path_traversal` consolidated to import the promoted primitive",
                  "product_id": "CSAFPID-0002"
                }
              }
            ]
          }
        ]
      }
    ]
  }
}

Are you sure?