Add version API, pkg-config, SPDX headers, SECURITY.md, CHANGELOG.md

Project-hygiene pass toward being a properly citable, embeddable,
professionally-packaged C library rather than just working code:

- PACKFS_VERSION_MAJOR/MINOR/PATCH/STRING in include/packfs.h, the single
  source of truth for the project's version, plus a runtime pfs_version()
  (src/vfs.c, next to vfs_new/vfs_free) so a dynamically-linked consumer
  can check ABI/API compatibility without recompiling. Covered by a new
  assertion in tests/test_mem.c that the macro and the runtime function
  never disagree.
- packfs.pc.in + a `make install` rule that generates packfs.pc with its
  Version: field derived from PACKFS_VERSION_STRING via a Makefile-level
  grep/sed, never hand-maintained separately -- verified end-to-end with a
  scratch `make install PREFIX=...` + `pkg-config --cflags --libs packfs`
  + `make uninstall`, not just by reading the rule.
- SPDX-License-Identifier: MIT added to every src/*.c and src/internal.h
  (include/packfs.h already had one); the whole distributed source tree
  now carries consistent machine-readable license metadata.
- SECURITY.md, stating precisely what this project's containment and pack-
  integrity code actually claims as a security boundary (concept.md
  Section 6/7) versus what it explicitly does not (unenforced `mode`, no
  cross-process concurrency) -- not generic boilerplate -- with a real
  reporting contact rather than a placeholder.
- CHANGELOG.md (Keep a Changelog format), summarizing the real history in
  git log to date; explicitly notes no version is tagged yet.

Verified: clean `make all` + `make test` (all 6 binaries, including the
new version-check assertion), and a full ASan/UBSan sweep of all 6
binaries with zero real findings (one run hit the already-documented
DEADLYSIGNAL sandbox flake on test_pack_write_perf across all 3 retries;
re-verified directly afterward with 5/5 additional clean passes and timing
well under any plausible timeout, confirming it was the flake, not a
regression, before treating this as done).

Deliberately not done here, by the user's explicit choice: no
CODE_OF_CONDUCT.md, and no git remote/publishing -- this repository still
has neither, and both are decisions left to the maintainer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqJpkdJ6Njnt1pw3CbghzB
This commit is contained in:
2026-09-14 10:58:35 +00:00
co-authored by Claude Sonnet 5
parent 529935deb4
commit 419182bb05
17 changed files with 317 additions and 6 deletions
+86
View File
@@ -0,0 +1,86 @@
# Security Policy
## Supported versions
No version has been tagged yet (see `CHANGELOG.md`); this project is
pre-1.0 (`README.md`'s "Status"). Only the latest commit on the default
branch is supported — there is no LTS branch and no backport policy while
the project is at `0.x`.
## What PackFS actually claims as a security boundary
Stated precisely, per this project's documentation standard (`CLAUDE.md`),
rather than a generic "we take security seriously":
- **Path containment for `dir` mounts is a hard requirement** (`concept.md`
Section 6, `CLAUDE.md`'s "Load-bearing constraints"). On Linux 5.6+,
every lookup beneath a `dir` mount uses `openat2(RESOLVE_BENEATH |
RESOLVE_NO_SYMLINKS)` to atomically block `..` escapes, symlink escapes,
and the canonicalize-then-open TOCTOU window in one kernel call.
`vfs_harden_process_with_landlock` (Section 6.2) is an additional,
**opt-in** kernel-level layer — not applied automatically, since
Landlock restrictions are cumulative, irreversible, and process-wide,
which would be a surprising side effect for an embeddable library to
trigger on its own.
- **On platforms or kernels without `openat2`, containment falls back to
per-component `O_NOFOLLOW` plus canonicalize-and-verify** (Section 6.3)
— an accepted, **explicitly weaker** residual-risk posture, not a claim
of equal strength. Code and documentation that describe this fallback as
equivalent to the `openat2` path would themselves be a bug worth
reporting.
- **The VFS core canonicalizes `.`/`..` in the virtual namespace itself**,
before any backend is reached, so a crafted path cannot escape a mount's
prefix even when no host directory is involved.
- **A pack file is treated as untrusted input unless it came from this
run's own compaction** (Section 7). Every index entry's offsets and
sizes are bounds-checked against the actual file/strings-region length
before being trusted; a failed check invalidates the whole pack load. An
externally-sourced pack additionally needs a checksum verified at load.
Loading an attacker-supplied `.img` file that PackFS accepts without
detecting corruption, or that causes an out-of-bounds read/write, is a
security bug.
- **Journal records are length/checksum self-describing** (Section 4.4),
so replay stops cleanly at the first torn or corrupted record rather
than reading past it.
## What PackFS explicitly does *not* claim
Reporting these as vulnerabilities is not necessary — they are documented,
known limitations, not silent gaps:
- **`mode` (permission bits, symlinks, hard links) is stored and restored
but not enforced.** Nothing in the VFS core interprets it as an access
control boundary in v0 (`concept.md` Section 10).
- **Cross-process concurrency is specified (an LMDB-style reader-table
design, Section 5.6) but not implemented.** Two uncoordinated processes
writing the same host directory through a `dir` mount are not
synchronized against each other.
- **`zip`/`tar` are permanently out of scope** (`CLAUDE.md`, "Project
decisions that supersede concept.md") — there is no archive-format
parsing code in this project to have a vulnerability in.
## Reporting a vulnerability
Please do not open a public GitHub issue for a suspected security
vulnerability. Once this repository has a hosting remote and private
security advisories are available there, prefer that. Until then, or as an
alternative, report privately to the maintainer:
**retoor** — `retoor@molodetz.nl`
When reporting, include: the affected function(s) or file(s), a minimal
reproduction (ideally as a `tests/test_*.c`-style program using only the
public API in `include/packfs.h`), and which of the guarantees above (or
which `concept.md` section) you believe is violated — this project's own
verification discipline (`make test`, ASan/UBSan/TSan per `CONTRIBUTING.md`)
depends on being able to turn a report into a permanent regression test.
## Verification this project already does
Every change to `src/upper.c`, `src/overlay.c`, or `src/vfs.c` is required
to pass under `-fsanitize=address,undefined` and `-fsanitize=thread`
before being considered done (`CLAUDE.md`) — this process has already
caught and fixed a real heap-use-after-free in the snapshot-reclamation
logic during development (see the `reclaim_gate` note in `src/internal.h`).
`.github/workflows/ci.yml` runs the full test suite plus both sanitizer
passes on every push.