Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ stricter subset of Keep a Changelog).

### Changed

- Auditing CRS type: does not require a target harness and produces bug-candidates
- `oss-crs export` and `oss-crs import` commands — imports/exports docker images/CRS source code to transfer to another host.
- Run-phase modules now default to `target_dependent: true`, so their images are built once per target during `build-target`. Set `target_dependent: false` for modules that can be built once during `prepare`.
- `--offline` flag for all subcommands: disables git fetch
Expand Down
5 changes: 3 additions & 2 deletions docs/config/crs.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,8 @@ The `supported_target` section defines what types of targets the CRS can work wi
|-------|------|----------|-------------|
| `mode` | `Set[TargetMode]` | Yes | Supported target modes (see [TargetMode](#targetmode)) |
| `language` | `Set[TargetLanguage]` | Yes | Supported programming languages (see [TargetLanguage](#targetlanguage)) |
| `sanitizer` | `Set[TargetSanitizer]` | Yes | Supported sanitizers (see [TargetSanitizer](#targetsanitizer)) |
| `architecture` | `Set[TargetArch]` | Yes | Supported CPU architectures (see [TargetArch](#targetarch)) |
| `sanitizer` | `Set[TargetSanitizer]` | No | Supported sanitizers (see [TargetSanitizer](#targetsanitizer)). Defaults to all sanitizers when omitted. |
| `architecture` | `Set[TargetArch]` | No | Supported CPU architectures (see [TargetArch](#targetarch)). Defaults to all architectures when omitted. |
| `fuzzing_engine` | `Set[FuzzingEngine]` | No | Supported fuzzing engines (see [FuzzingEngine](#fuzzingengine)). Defaults to all engines when omitted. |

### Example
Expand Down Expand Up @@ -271,6 +271,7 @@ Defines the type of CRS:
| `bug-fixing-ensemble` | Ensemble CRS that aggregates bug-fixing outputs |
| `bug-finding-triage` | Post-processor CRS that triages bug-finding results (e.g. deduplication, validation). Reads from the main exchange dir and writes to the processed exchange dir. |
| `seed-filter` | Post-processor CRS that filters or prioritizes seeds/inputs for downstream CRS. Reads from the main exchange dir and writes to the processed exchange dir. |
| `auditing` | Source-auditing CRS that analyzes `OSS_CRS_TARGET_SOURCE` and submits `bug-candidate` artifacts. It may run with or without a target harness. |

### TargetMode

Expand Down
63 changes: 44 additions & 19 deletions docs/config/target-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Full spec (for OSS-Fuzz compatibility/reference): [OSS-Fuzz project.yaml referen

## Usage

### Harnessed Run

```bash
# Build target
uv run oss-crs build-target \
Expand All @@ -46,29 +48,34 @@ uv run oss-crs run \
--target-harness <harness_name>
```

| Argument | Required | Description |
|-----------------------|----------|--------------------------------------------------------------------------------|
| `--fuzz-proj-path` (`--target-path`, `--target-proj-path`, deprecated aliases) | Yes | Path to the OSS-Fuzz target project directory (`Dockerfile`, `build.sh`; `project.yaml` optional). |
| `--target-source-path` | No | Optional local source override path. If set, source is synchronized with `rsync -a --delete` into the effective Dockerfile `WORKDIR`. |
| `--target-harness` | Yes (run)| Fuzz target harness binary name. |
### Source-Only Run

Existing [OSS-Fuzz projects](https://github.com/google/oss-fuzz/tree/master/projects) can be used directly as `--fuzz-proj-path` without modification.
```bash
uv run oss-crs run \
--compose-file ./crs-compose.yaml \
--target-source-path /path/to/source
```

### Source Path Semantics
Source-only runs omit `--fuzz-proj-path` entirely. The source path is
directly bind-mounted to `OSS_CRS_TARGET_SOURCE`. There is no build step,
no `OSS_CRS_FUZZ_PROJ` mount, and `SANITIZER`, `ARCHITECTURE`,
`FUZZING_LANGUAGE`, etc. not injected into source-only containers.

- `OSS_CRS_PROJ_PATH` points to the copied target project directory.
- `OSS_CRS_REPO_PATH` points to the effective final Dockerfile `WORKDIR` inside
the target image.
- `WORKDIR` resolution follows Dockerfile semantics, with fallback chain:
final `WORKDIR` -> `$SRC` -> `/src` (when `SRC` is not provided).
- `libCRS download-source repo` prefers the live runtime source workspace
rooted at `$SRC`/`/src`. When `OSS_CRS_REPO_PATH` is inside that workspace,
the downloaded tree preserves the workspace layout rather than flattening a
nested `WORKDIR`.
- When `--target-source-path` is set, the override source is synchronized into
`OSS_CRS_REPO_PATH` via `rsync -a --delete`.
## Arguments

| Argument | Required | Description |
|----------|----------|-------------|
| `--fuzz-proj-path` (`--target-path`, `--target-proj-path`, deprecated aliases) | Yes for harnessed/harness-gen runs; omitted for source-only runs | Path to the OSS-Fuzz target project directory (`Dockerfile`, `build.sh`; `project.yaml` optional). |
| `--target-source-path` | Required for source-only runs; optional local source override otherwise | Path to the source tree. For source-only runs, this is directly bind-mounted. For harnessed runs, source is synchronized with `rsync -a --delete` into the effective Dockerfile `WORKDIR`. |
| `--target-harness` | Yes (run) | Fuzz target harness binary name. |

### `--target-source-path` Sync Flow
At least one of `--fuzz-proj-path` or `--target-source-path` is required.

Existing [OSS-Fuzz projects](https://github.com/google/oss-fuzz/tree/master/projects) can be used directly as `--fuzz-proj-path` without modification.

## Source Path Semantics

### Harnessed Runs with Source Override

`--target-source-path` is not bind-mounted directly to `OSS_CRS_REPO_PATH`.
Instead, during image build:
Expand All @@ -78,3 +85,21 @@ Instead, during image build:
3. `rsync -a --delete /OSS_CRS_REPO_OVERRIDE/ ./` runs from the effective
`WORKDIR`.
4. `OSS_CRS_REPO_PATH` points to that effective `WORKDIR` path.

### Source-Only Runs

For source-only runs, `--target-source-path` is directly bind-mounted to
`OSS_CRS_TARGET_SOURCE`. There is no image build, no `OSS_CRS_FUZZ_PROJ`
mount, and environment variables like `SANITIZER`, `ARCHITECTURE`, `FUZZING_LANGUAGE`,
etc. are not injected.

### Common Semantics

- `OSS_CRS_REPO_PATH` points to the effective final Dockerfile `WORKDIR`
inside the target image.
- `WORKDIR` resolution follows Dockerfile semantics, with fallback chain:
final `WORKDIR` -> `$SRC` -> `/src` (when `SRC` is not provided).
- `libCRS download-source repo` prefers the live runtime source workspace
rooted at `$SRC`/`/src`. When `OSS_CRS_REPO_PATH` is inside that workspace,
the downloaded tree preserves the workspace layout rather than flattening a
nested `WORKDIR`.
13 changes: 10 additions & 3 deletions docs/crs-development-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ The `crs.yaml` file is the central configuration for your CRS. It tells OSS-CRS
```yaml
name: my-crs
type:
- bug-finding # bug-finding, bug-fixing, or both
- bug-finding # bug-finding, bug-fixing, etc.
version: "1.0.0"
docker_registry: "ghcr.io/my-org/my-crs"

Expand Down Expand Up @@ -131,7 +131,7 @@ required_envs:
| Field | Description |
|---|---|
| `name` | Unique name for your CRS |
| `type` | Set of CRS types: `bug-finding`, `bug-fixing` |
| `type` | Set of CRS capabilities: `bug-finding`, `bug-fixing`, etc. |
| `version` | Version string (used as a Docker image tag) |
| `docker_registry` | Docker registry URL for your CRS images |
| `prepare_phase.hcl` | Path to the HCL file for `docker buildx bake` |
Expand Down Expand Up @@ -346,7 +346,7 @@ Your containers receive these environment variables automatically:
| `OSS_CRS_NAME` | CRS name (from `crs-compose.yaml`) | `my-crs` |
| `OSS_CRS_SERVICE_NAME` | Full service name | `my-crs_fuzzer` |
| `OSS_CRS_TARGET` | Target project name | `libxml2` |
| `OSS_CRS_TARGET_HARNESS` | Target harness binary name | `xml` |
| `OSS_CRS_TARGET_HARNESS` | Target harness binary name. Unset for no-harness source-level runs. | `xml` |
| `OSS_CRS_CPUSET` | Allocated CPU cores | `4-7` |
| `OSS_CRS_MEMORY_LIMIT` | Memory limit | `16G` |
| `OSS_CRS_BUILD_OUT_DIR` | Build output directory (read-only at run time) | `/OSS_CRS_BUILD_OUT_DIR` |
Expand Down Expand Up @@ -799,6 +799,12 @@ Your CRS should submit findings through libCRS:
- **`register-submit-dir`** — Best for high-volume output. Forks a daemon that watches the directory, deduplicates files by hash, and submits in batches. Use this for seeds and PoVs.
- **`submit`** — Best for one-off submissions. Submits a single file immediately.

### Source-Level Bug Finding Without A Harness

Source-only runs are invoked without `--fuzz-proj-path` and instead use `--target-source-path` to point at the source tree. They skip OSS-Fuzz target image builds and require every run module to set `target_dependent: false`; run `oss-crs prepare` first to build those target-independent images. They do not mount `OSS_CRS_BUILD_OUT_DIR` or `OSS_CRS_FUZZ_PROJ`, but still receive `OSS_CRS_SUBMIT_DIR`, `OSS_CRS_FETCH_DIR`, `OSS_CRS_SHARED_DIR`, `OSS_CRS_LOG_DIR`, and `OSS_CRS_TARGET_SOURCE`. They do not receive `OSS_CRS_TARGET_HARNESS`, `SANITIZER`, `ARCHITECTURE`, `FUZZING_LANGUAGE`, `FUZZING_ENGINE`, `HELPER`, or `RUN_FUZZER_MODE`, since none of these are consumed by source-level analysis.

Source-only runs require all CRSs to be of type `auditing`. This ensures that only CRSs designed to analyze source code without a compiled target can run in source-only mode. An `auditing` CRS is a regular producer that reads `OSS_CRS_TARGET_SOURCE` and submits `bug-candidate` artifacts. The type is a capability label rather than a source-only restriction: auditors may run alone without `--target-harness` or alongside harness-based CRSs.

---

## Fetching Data
Expand Down Expand Up @@ -914,6 +920,7 @@ Before publishing your CRS, verify:
- [ ] Run-phase Dockerfiles install libCRS (`COPY --from=libcrs . /opt/libCRS && RUN /opt/libCRS/install.sh`)
- [ ] Containers download build outputs at startup via `libCRS download-build-output`
- [ ] Artifact directories are registered with `libCRS register-submit-dir`
- [ ] Auditing CRSs submit findings as `bug-candidate` artifacts
- [ ] `supported_target` accurately reflects your CRS capabilities
- [ ] `required_llms` lists all models used (if any)
- [ ] `required_inputs` lists inputs the CRS depends on (if any)
Expand Down
2 changes: 1 addition & 1 deletion docs/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ source:
| Field | Description |
|---|---|
| `name` | Unique identifier for the CRS |
| `type` | List of CRS capabilities — `bug-finding`, `bug-fixing`, `bug-finding-triage`, `seed-filter`, `bug-fixing-ensemble`, or a combination |
| `type` | List of CRS capabilities — `bug-finding`, `bug-fixing`, `auditing`, `bug-finding-triage`, `seed-filter`, `bug-fixing-ensemble`, or a combination |
| `source.url` | Git repository URL containing the CRS implementation |
| `source.ref` | Git branch or tag to use |

Expand Down
52 changes: 28 additions & 24 deletions oss_crs/src/cli/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@
from ..target import Target


def handle_archive(args, crs_compose, target: Target) -> bool:
def handle_archive(
args, crs_compose, target: Target, *, unharnessed: bool = False
) -> bool:
"""Handle the archive command."""
ctx = resolve_run_context(args, crs_compose, target)
ctx = resolve_run_context(args, crs_compose, target, unharnessed=unharnessed)
if ctx is None:
return False
sanitizer, run_id = ctx
harness = target.target_harness
sanitizer, run_id, scope_known = ctx
if not scope_known:
print("No run artifact scope found for the selected run.", file=sys.stderr)
return False
work_dir = crs_compose.work_dir

out_path = Path(args.out)
Expand All @@ -25,8 +29,9 @@ def handle_archive(args, crs_compose, target: Target) -> bool:
triage_crs = [crs for crs in crs_compose.crs_list if crs.config.is_triage]
non_triage_crs = [crs for crs in crs_compose.crs_list if not crs.config.is_triage]

# Collect (arcname, src_path) pairs for each artifact subdir
artifact_subdirs = list(SUBMITTED_ARTIFACT_DIR_NAMES)
# Collect (arcname, src_path) pairs for each artifact subdir.
# Include "harnesses" locally (harness-gen output is not exchanged).
artifact_subdirs = list(SUBMITTED_ARTIFACT_DIR_NAMES) + ["harnesses"]
# In a triage run, POVs come from the triage CRS instead (see below).
non_pov_subdirs = [s for s in artifact_subdirs if s not in ("povs", "reports")]

Expand Down Expand Up @@ -81,27 +86,26 @@ def _add_dir(collected: list, src_dir: Path, arcname_prefix: str) -> None:

if args.include_all:
# Also include exchange dir, logs, and shared dirs
if harness:
exchange_dir = work_dir.get_exchange_dir(
target, run_id, sanitizer, create=False
exchange_dir = work_dir.get_exchange_dir(
target, run_id, sanitizer, create=False
)
_add_dir(collected, exchange_dir, "exchange")

run_logs_dir = work_dir.get_run_logs_dir(
target, run_id, sanitizer, create=False
)
_add_dir(collected, run_logs_dir, "logs")

for crs in crs_compose.crs_list:
shared_dir = work_dir.get_shared_dir(
crs.name, target, run_id, sanitizer, create=False
)
_add_dir(collected, exchange_dir, "exchange")
_add_dir(collected, shared_dir, f"shared/{crs.name}")

run_logs_dir = work_dir.get_run_logs_dir(
target, run_id, sanitizer, create=False
log_dir = work_dir.get_log_dir(
crs.name, target, run_id, sanitizer, create=False
)
_add_dir(collected, run_logs_dir, "logs")

for crs in crs_compose.crs_list:
shared_dir = work_dir.get_shared_dir(
crs.name, target, run_id, sanitizer, create=False
)
_add_dir(collected, shared_dir, f"shared/{crs.name}")

log_dir = work_dir.get_log_dir(
crs.name, target, run_id, sanitizer, create=False
)
_add_dir(collected, log_dir, f"logs/crs/{crs.name}")
_add_dir(collected, log_dir, f"logs/crs/{crs.name}")

if not collected:
print("No artifacts found for the selected run.", file=sys.stderr)
Expand Down
Loading