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
2 changes: 1 addition & 1 deletion .github/workflows/bridge-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#
# Review notes:
# - B2 (major -> review) is left to the existing human-review path.
# - Production hardening: a released binary or a hyperpolymath/standards reusable workflow.
# - Production hardening: a released binary or a metadatastician/standards reusable workflow.
# - github context via env: only (no ${{ }} in run lines) per estate rules.
name: bridge-gate
on:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dogfood-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:

:warning: **No .a2ml/.deed manifest files found.** Every RSR-compliant repo should have a repo deed (`<reponame>_chora.deed`) at its root.

Author them per hyperpolymath/standards `1-formats/k9/spec/K9-CONTRACT-SPEC.adoc` (D173).
Author them per metadatastician/standards `1-formats/k9/spec/K9-CONTRACT-SPEC.adoc` (D173).
EOF
else
echo "## K9 Contract Validation" >> "$GITHUB_STEP_SUMMARY"
Expand Down
7 changes: 5 additions & 2 deletions docs/php-coverage.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ recognition of extensionless or custom-language source.
Coverage `path` strings use lossy UTF-8 for readability. On Unix, non-UTF-8
paths additionally include `path_bytes_hex`, the hex-encoded original path
bytes; Unix readers use these bytes to restore the exact path. Older reports
without this field still load. In SARIF, both representations refer to the
path relative to the scan root when that prefix matches.
without this field still load. In SARIF, `to_sarif` uses the scan root directory
as the base, or the scan root's parent directory when the scan root is a file.
Both representations are made relative to this base when that prefix matches;
paths outside the base are retained. Unix readers should resolve `path_bytes_hex`
using this same base.

A source read is bounded to 16 MiB. Read/decode/parse failures retain the other
results and leave incomplete coverage. There is not yet a global analysis
Expand Down
8 changes: 5 additions & 3 deletions src/report/sarif.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ fn parse_location(loc: &str) -> (&str, Option<u32>) {
}

/// Convert findings to a SARIF 2.1.0 log with coverage in the run's properties.
/// Coverage paths are made relative to the target directory (or the parent
/// of a file target) when that prefix matches; other paths are retained.
/// Coverage and finding paths are made relative to the target directory (or
/// the parent of a file target) when that prefix matches; other paths are retained.
///
/// # Errors
/// Propagates errors serialising coverage to a JSON value.
Expand Down Expand Up @@ -228,6 +228,8 @@ pub fn to_sarif(report: &AssailReport) -> Result<SarifLog> {
.map(|wp| {
let loc_str = wp.location.as_deref().unwrap_or("unknown");
let (path, line) = parse_location(loc_str);
let path = std::path::Path::new(path);
let path = path.strip_prefix(root).unwrap_or(path);

SarifResult {
rule_id: rule_id(&wp.category).to_string(),
Expand All @@ -238,7 +240,7 @@ pub fn to_sarif(report: &AssailReport) -> Result<SarifLog> {
locations: vec![SarifLocation {
physical_location: SarifPhysicalLocation {
artifact_location: SarifArtifactLocation {
uri: path.to_string(),
uri: path.to_string_lossy().into_owned(),
},
region: line.map(|l| SarifRegion { start_line: l }),
},
Expand Down
39 changes: 39 additions & 0 deletions tests/sarif_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,45 @@ fn test_sarif_from_real_analysis() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

#[test]
fn finding_paths_match_coverage_for_directory_and_file_roots(
) -> Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let root = dir.path().join("scan");
std::fs::create_dir_all(root.join("src"))?;
let input = root.join("src/input.php");
std::fs::write(&input, "<?php echo 'ok';")?;
let outside = dir.path().join("scan-other/outside.php");

for (target, expected) in [(&root, "src/input.php"), (&input, "input.php")] {
let mut report = assail::analyze(target)?;
report.weak_points = make_test_report().weak_points;
report.weak_points[0].location = Some(format!("{}:17", input.display()));
report.weak_points[1].location = Some(outside.display().to_string());

let log = sarif::to_sarif(&report)?;
let run = &log.runs[0];
let location = &run.results[0].locations[0].physical_location;
assert_eq!(location.artifact_location.uri, expected);
assert_eq!(
run.properties["coverage"]["inputs"][0]["path"],
location.artifact_location.uri
);
assert_eq!(
location.region.as_ref().ok_or("missing line")?.start_line,
17
);
let outside_location = &run.results[1].locations[0].physical_location;
assert_eq!(
outside_location.artifact_location.uri,
outside.to_string_lossy()
);
assert!(outside_location.region.is_none());
assert_eq!(report.coverage.inputs[0].path, input);
}
Ok(())
}

#[cfg(target_os = "linux")]
#[test]
fn coverage_paths_preserve_relative_bytes_in_sarif() {
Expand Down