Repository navigation
Add eight sdkharness conformance checks: file operations and bucket CRUD - #220
Conversation
|
Reviewed scope, the security guards, and each scenario. Well-scoped — only Required (blocking)1. Snyk Code is red (1 new finding) — needs to be green before merge. # .snyk
version: v1.5.0
ignore:
'<SNYK-RULE-ID-FROM-THE-CHECK>':
- '.sdkharness/tests/conformance/Support.java':
reason: >-
SHA-1 is B2's required upload integrity checksum (X-Bz-Content-Sha1),
not a cryptographic/security primitive. Test-only; not in any source set.
expires: 2027-10-13T00:00:00.000Z(Or ignore it in the Snyk UI with the same reason — either way, confirm the rule/line first in case it's not the SHA-1 one.) 2. A failed cleanup must fail the check. Right now private static final List<String> cleanupErrors = new ArrayList<>();
private static void cleanup() {
if (client == null) return;
for (B2Bucket bucket : created) {
try {
client.deleteAllFilesInBucket(bucket.getBucketId());
client.deleteBucket(bucket.getBucketId());
} catch (B2Exception | RuntimeException e) {
cleanupErrors.add(bucket.getBucketName() + ": " + describe(e));
}
}
try { client.close(); } catch (RuntimeException e) { /* nothing left to release */ }
}…and in if (failure == null && !cleanupErrors.isEmpty()) {
failure = "cleanup -- left " + cleanupErrors.size() + " bucket(s): "
+ tidy(String.join("; ", cleanupErrors));
}That attempts every deletion and still turns a leaked bucket into a FAIL. 3. Enforce the loopback boundary at request time (or soften the claim). The guard at private static HttpClientFactory guardedFactory(RequestConfig requestConfig, boolean recordWire) {
return new HttpClientFactory() {
@Override public CloseableHttpClient create() {
return HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.addInterceptorFirst((HttpRequestInterceptor) (request, context) -> {
final HttpHost target = (HttpHost) context.getAttribute(
HttpCoreContext.HTTP_TARGET_HOST); // confirm this is populated pre-send
final String host = target == null ? null : target.getHostName();
if (!"127.0.0.1".equals(host)) {
throw new HttpException("blocked non-loopback host: " + host);
}
if (recordWire) {
wire.add(request.getRequestLine().getMethod() + " " + request.getRequestLine().getUri());
}
})
.build();
}
@Override public void close() {}
};
}and have Recommended (non-blocking)
Keep as-isThe VerdictRequest changes — happy to approve once Snyk is green, a failed cleanup fails the check, and the loopback boundary is enforced (or the claim scoped). Everything else is solid. |
…ime, anchor the verdict match - A bucket or file the check could not delete now fails the verdict; every deletion is still attempted and the single result line is still printed. - Every check builds its client from one guarded HTTP client factory that refuses any request whose target host is not 127.0.0.1 (redirect hops included) and fails the verdict if one was attempted. GuardSelfTest proves it. - run-conformance matches the result line at column 1 with index(), not anywhere in a line. - README: say what is guaranteed (formatting and capping, not redaction). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Summary
Adds eight
conformancescenarios to the.sdkharness/contract (health is unchanged), so Backblaze's SDK quality harness can ask whether this SDK really does what its capability card claims. This is the first slice: the core file and bucket operations..sdkharness/tests.tsv: eight rows,conformance <capability> simulator ./.sdkharness/tests/run-conformance, forfiles.upload,files.download_content,files.download_by_id,files.list,files.delete_version,files.hide,files.metadataandbucket.crud..sdkharness/tests/run-conformance(bash, executable): same guards asrun-health(loopback simulator URL only, every ambientB2_*dropped, fixed test credential). It compilesconformance/Support.javaand the scenario's class withjavac --release 11against the jars the harness built, runs it, and prints oneSDKHARNESS_RESULTline. A JDK 11 single-file launch cannot import a second source file, which is why the shared helper is compiled rather than imported..sdkharness/tests/conformance/*.java: one program per scenario, using only the SDK's public API. Each one cites the B2 API pages its assertions come from, makes its ownsdkharness-conf-*buckets, and deletes everything it created, on failure too..sdkharness/README.md: what each scenario asserts and how to run one by hand.files.metadataispartialon the card (by name is emulated with an HTTP HEAD of the download URL). The check records the SDK's own HTTP requests and asserts that boundary: by id is aPOSTtob2_get_file_info, by name is aHEADof/file/<bucket>/<name>.No build or source changes. The Gradle files,
core/andhttpclient/are untouched, and.sdkharness/is in no source set. There is no test in this repository that pins the contract file.Verification
Run through the harness adapter against the pinned standalone simulator (
backblaze-labs/b2-simulatorat21a4002), with the jars the harness built from this branch's revision, under a JDK 17 launcher (and the wrapper by hand under JDK 11 forfiles.list,files.metadata,bucket.crud):files.download_content,files.download_by_id,files.list,files.delete_version,files.hide,bucket.crud,files.metadatafiles.uploadunusual name: a file namedst/upload/sp ace/...comes back asst/upload/sp+ace/...The
files.uploadfailure is a finding, not a loosened check. B2 documentsX-Bz-File-Nameas "percent-encoded UTF-8. For example, spaces should be replaced with %20" (b2_upload_file).B2StringUtil.urlEncodeisURLEncoder.encode(s, UTF8).replace("%2F", "/"), which writes a space as+, so the stored name contains a literal plus. The other legs offiles.upload(size, sha1, content type, fileInfo, listing, byte-identical download, empty file) pass before that leg runs. Whether to change the encoding is the maintainers' call; the check will go green when it does../gradlew buildis unaffected by this change.Merge note
A merge to
masterruns the existingci_cd.ymlpush steps (upload to b2, Javadoc togh-pages); this PR does not change them.Tracking: backblaze-labs/demand-side-ai#1101