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
18 changes: 16 additions & 2 deletions limacharlie/commands/cloudsec.py
Original file line number Diff line number Diff line change
Expand Up @@ -1602,16 +1602,29 @@ def code_rescan(ctx, repo, ref, provider) -> None:
@click.option("--commit", default=None,
help="The revision the document describes. Recorded, not verified.")
@click.option("--ref", default=None, help="The branch or tag, for context.")
@click.option("--default-branch", "default_branch", default=None,
help="The repository's shipping branch. Only worth sending for a "
"repository LimaCharlie does not collect — nothing else can "
"state it there.")
@click.option("--provider", default=None,
help="Source-control provider the key belongs to (default github).")
@pass_context
def code_ingest(ctx, repo, source, file_path, commit, ref, provider) -> None:
def code_ingest(ctx, repo, source, file_path, commit, ref, default_branch, provider) -> None:
"""Push scan results your own pipeline produced for one repository.

Findings are deduplicated against the hosted scan by IDENTITY, so
pushing something the hosted scanner also found updates it rather than
duplicating it, and re-pushing an identical document writes nothing.

The repository does not have to be one LimaCharlie collects. Pushing for
a repository no connected source-control organization covers creates it,
carrying only what the push vouches for, and 'cloudsec code repos' shows
it with source 'ingest' (and 'both' once a connection collects it too).
Such a repository counts against the free tier's repository quota exactly
like a collected one, and is removed with its findings if it goes a month
with no push that moves its commit. It must still be selected by an
enabled code_scanning policy.

Two things in the response are worth reading rather than skimming.
'notes' lists what the FORMAT could not carry — 'secrets_not_ingestable'
means credential findings were deliberately dropped, because a pushed
Expand All @@ -1635,7 +1648,8 @@ def code_ingest(ctx, repo, source, file_path, commit, ref, provider) -> None:
"sections separately." % (file_path, len(document), MAX_CODE_INGEST_BYTES))
cs = _get_cloudsec(ctx)
_output(ctx, cs.ingest_code_results(
repo, source, document, commit=commit, ref=ref, provider=provider))
repo, source, document, commit=commit, ref=ref,
default_branch=default_branch, provider=provider))


@code_group.command("scan")
Expand Down
20 changes: 17 additions & 3 deletions limacharlie/sdk/cloudsec.py
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,7 @@ def ingest_code_results(
*,
commit: str | None = None,
ref: str | None = None,
default_branch: str | None = None,
provider: str | None = None,
) -> dict[str, Any]:
"""Push results your own pipeline produced for one repository.
Expand All @@ -1345,9 +1346,16 @@ def ingest_code_results(

Args:
repo: the ``"<owner>/<name>"`` key :meth:`list_code_repos`
returns. It must already be in the org's collected
inventory and be selected by an enabled ``code_scanning``
policy — the same switch the hosted lane uses.
returns. It must be selected by an enabled ``code_scanning``
policy — the same switch the hosted lane uses — but it does
NOT have to be in the org's collected inventory: pushing for
a repository no connected source-control organization covers
creates it, with only the facts the push vouches for and
``source: "ingest"`` on :meth:`list_code_repos`. Such a
repository counts against the free tier's repository quota
exactly like a collected one, and is removed along with its
findings if it goes a month with no push that moves its
commit.
source: ``"sarif"``, ``"cyclonedx"`` or ``"report"`` (the
LimaCharlie scanner's own ``report/v1`` document, which is
loss-free and therefore dedupes exactly).
Expand All @@ -1356,6 +1364,10 @@ def ingest_code_results(
commit: the revision the document describes. Recorded, not
verified, and worth sending: it is what tells somebody
reading a finding which checkout produced it.
default_branch: the repository's shipping branch. Only worth
sending for a repository LimaCharlie does not collect —
nothing else can state it there, and it is left unset rather
than guessed when you do not know it.

Returns:
``{"result": {...}}`` — what landed: ``findings``, the SoR
Expand Down Expand Up @@ -1386,6 +1398,8 @@ def ingest_code_results(
body["commit"] = commit
if ref is not None:
body["ref"] = ref
if default_branch is not None:
body["default_branch"] = default_branch
if provider is not None:
body["provider"] = provider
return self._post("code/ingest", body)
Expand Down
9 changes: 9 additions & 0 deletions tests/unit/test_sdk_cloudsec.py
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,15 @@ def test_ingest_code_results_sends_an_object_as_an_object(self, cs, mock_org):
assert body["document"] == {"version": "2.1.0", "runs": []}
assert "document_b64" not in body

def test_ingest_code_results_sends_the_default_branch(self, cs, mock_org):
"""The shipping branch reaches the backend, because for a repository
LimaCharlie does not collect nothing else can state it — and it is
omitted, never guessed, when the caller does not know it."""
mock_org.client.request.return_value = {"result": {}}
cs.ingest_code_results("byo-only/demo", "sarif", "{}", default_branch="trunk")
_, body = _post_call(mock_org)
assert body["default_branch"] == "trunk"

def test_ingest_code_results_omits_what_was_not_given(self, cs, mock_org):
"""An absent commit must not become an empty one: '' is a value, and a
finding stamped with a blank revision is worse than one with none."""
Expand Down