Skip to content

Commit e2eba7d

Browse files
hyperpolymathcoderabbitai[bot]CodeRabbit
authored
Add explicit assail coverage and native PHP analysis (#241)
PHP source previously fell out of traversal, while file_statistics only described files with nonzero counters. Assail schema 2.6 adds an independent input ledger, content digests, explicit exclusions/failures and analysis-depth descriptions. --fail-on-incomplete emits the report then exits 2 for partial, failed or empty scans. Legacy reports retain unknown coverage. Adds pinned PHP parsing and local heuristic checks for empty callables, BLAKE3/BLAKE2b contract substitution and unserialize calls without options. Malformed PHP is a coverage gap. Traversal records pruned directories, avoids symlink cycles and bounds each source read to 16 MiB. Headless stdout remains parseable JSON; JSON, SARIF and the Chapel CLI contract expose the new schema. Validation: 624 tests passed with the sarif feature; 4 pre-existing tests remain ignored. Formatting, clippy with warnings denied and the Chapel contract check passed. All eleven portfolio targets were rescanned in strict mode and retain explicit coverage failures where adapters are missing. PHP fixtures include vulnerable and safe syntax, templates, namespaces, traits, closures and malformed input. PHP analysis is local T0 evidence. Cross-file resolution, contextual taint, typed graph evidence and dynamic stress/fuzz campaigns remain separate milestones. Existing non-PHP heuristics are not presented as parser or dataflow proofs. The new tree-sitter dependency requires an updated cc build dependency; the lockfile records that transitive change. Shared workflow references use the verified canonical metadatastician/standards repository. Existing commit pins and permissions are preserved; no checks are disabled. CI identified RUSTSEC-2026-0382 in the existing lockfile. rustls-webpki is now pinned to patched 0.103.16, with the full local suite and Clippy rerun successfully. Remote CI is not fully green: GitHub's repository Actions allowlist rejects the shared reusable workflows at their canonical metadatastician/standards location. Exact pinned references require administrator policy review; no policy was relaxed. Review all current checks before merging. The retained coverage gaps are explicit, not suppressed. --------- Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
1 parent deda819 commit e2eba7d

5 files changed

Lines changed: 51 additions & 7 deletions

File tree

‎.github/workflows/bridge-gate.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#
1919
# Review notes:
2020
# - B2 (major -> review) is left to the existing human-review path.
21-
# - Production hardening: a released binary or a hyperpolymath/standards reusable workflow.
21+
# - Production hardening: a released binary or a metadatastician/standards reusable workflow.
2222
# - github context via env: only (no ${{ }} in run lines) per estate rules.
2323
name: bridge-gate
2424
on:

‎.github/workflows/dogfood-gate.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ jobs:
9898
9999
:warning: **No .a2ml/.deed manifest files found.** Every RSR-compliant repo should have a repo deed (`<reponame>_chora.deed`) at its root.
100100
101-
Author them per hyperpolymath/standards `1-formats/k9/spec/K9-CONTRACT-SPEC.adoc` (D173).
101+
Author them per metadatastician/standards `1-formats/k9/spec/K9-CONTRACT-SPEC.adoc` (D173).
102102
EOF
103103
else
104104
echo "## K9 Contract Validation" >> "$GITHUB_STEP_SUMMARY"

‎docs/php-coverage.adoc‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ recognition of extensionless or custom-language source.
2424
Coverage `path` strings use lossy UTF-8 for readability. On Unix, non-UTF-8
2525
paths additionally include `path_bytes_hex`, the hex-encoded original path
2626
bytes; Unix readers use these bytes to restore the exact path. Older reports
27-
without this field still load. In SARIF, both representations refer to the
28-
path relative to the scan root when that prefix matches.
27+
without this field still load. In SARIF, `to_sarif` uses the scan root directory
28+
as the base, or the scan root's parent directory when the scan root is a file.
29+
Both representations are made relative to this base when that prefix matches;
30+
paths outside the base are retained. Unix readers should resolve `path_bytes_hex`
31+
using this same base.
2932

3033
A source read is bounded to 16 MiB. Read/decode/parse failures retain the other
3134
results and leave incomplete coverage. There is not yet a global analysis

‎src/report/sarif.rs‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ fn parse_location(loc: &str) -> (&str, Option<u32>) {
178178
}
179179

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

232234
SarifResult {
233235
rule_id: rule_id(&wp.category).to_string(),
@@ -238,7 +240,7 @@ pub fn to_sarif(report: &AssailReport) -> Result<SarifLog> {
238240
locations: vec![SarifLocation {
239241
physical_location: SarifPhysicalLocation {
240242
artifact_location: SarifArtifactLocation {
241-
uri: path.to_string(),
243+
uri: path.to_string_lossy().into_owned(),
242244
},
243245
region: line.map(|l| SarifRegion { start_line: l }),
244246
},

‎tests/sarif_tests.rs‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,45 @@ fn test_sarif_from_real_analysis() -> Result<(), Box<dyn std::error::Error>> {
204204
Ok(())
205205
}
206206

207+
#[test]
208+
fn finding_paths_match_coverage_for_directory_and_file_roots(
209+
) -> Result<(), Box<dyn std::error::Error>> {
210+
let dir = tempfile::tempdir()?;
211+
let root = dir.path().join("scan");
212+
std::fs::create_dir_all(root.join("src"))?;
213+
let input = root.join("src/input.php");
214+
std::fs::write(&input, "<?php echo 'ok';")?;
215+
let outside = dir.path().join("scan-other/outside.php");
216+
217+
for (target, expected) in [(&root, "src/input.php"), (&input, "input.php")] {
218+
let mut report = assail::analyze(target)?;
219+
report.weak_points = make_test_report().weak_points;
220+
report.weak_points[0].location = Some(format!("{}:17", input.display()));
221+
report.weak_points[1].location = Some(outside.display().to_string());
222+
223+
let log = sarif::to_sarif(&report)?;
224+
let run = &log.runs[0];
225+
let location = &run.results[0].locations[0].physical_location;
226+
assert_eq!(location.artifact_location.uri, expected);
227+
assert_eq!(
228+
run.properties["coverage"]["inputs"][0]["path"],
229+
location.artifact_location.uri
230+
);
231+
assert_eq!(
232+
location.region.as_ref().ok_or("missing line")?.start_line,
233+
17
234+
);
235+
let outside_location = &run.results[1].locations[0].physical_location;
236+
assert_eq!(
237+
outside_location.artifact_location.uri,
238+
outside.to_string_lossy()
239+
);
240+
assert!(outside_location.region.is_none());
241+
assert_eq!(report.coverage.inputs[0].path, input);
242+
}
243+
Ok(())
244+
}
245+
207246
#[cfg(target_os = "linux")]
208247
#[test]
209248
fn coverage_paths_preserve_relative_bytes_in_sarif() {

0 commit comments

Comments
 (0)