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
4 changes: 2 additions & 2 deletions app/cli/documentation/cli-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ Options
--annotation strings additional annotation in the format of key=value
--attestation-id string Unique identifier of the in-progress attestation
-h, --help help for add
--kind string kind of the material to be recorded: ["ARTIFACT" "ASYNCAPI_SPEC" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_AI_AGENT_CONFIG" "CHAINLOOP_AI_CODING_SESSION" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "GITLEAKS_JSON" "GRAPHQL_SPEC" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENAPI_SPEC" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "SYSINTERNALS_SIGCHECK" "TWISTCLI_SCAN_JSON" "YELP_DETECT_SECRETS_BASELINE" "ZAP_DAST_ZIP"]
--kind string kind of the material to be recorded: ["ARTIFACT" "ASYNCAPI_SPEC" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_AI_AGENT_CONFIG" "CHAINLOOP_AI_CODING_SESSION" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "GITLEAKS_JSON" "GRAPHQL_SPEC" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENAPI_SPEC" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "SYSINTERNALS_ACCESSCHK" "SYSINTERNALS_SIGCHECK" "TWISTCLI_SCAN_JSON" "YELP_DETECT_SECRETS_BASELINE" "ZAP_DAST_ZIP"]
--name string name of the material as shown in the contract
--no-strict-validation skip strict schema validation for structured materials (SBOM_CYCLONEDX_JSON, OPENAPI_SPEC, ASYNCAPI_SPEC)
--registry-password string registry password, ($CHAINLOOP_REGISTRY_PASSWORD)
Expand Down Expand Up @@ -3025,7 +3025,7 @@ Options
--annotation strings Key-value pairs of material annotations (key=value)
-h, --help help for eval
--input stringArray Key-value pairs of policy inputs (key=value)
--kind string Kind of the material: ["ARTIFACT" "ASYNCAPI_SPEC" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_AI_AGENT_CONFIG" "CHAINLOOP_AI_CODING_SESSION" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "GITLEAKS_JSON" "GRAPHQL_SPEC" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENAPI_SPEC" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "SYSINTERNALS_SIGCHECK" "TWISTCLI_SCAN_JSON" "YELP_DETECT_SECRETS_BASELINE" "ZAP_DAST_ZIP"]
--kind string Kind of the material: ["ARTIFACT" "ASYNCAPI_SPEC" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_AI_AGENT_CONFIG" "CHAINLOOP_AI_CODING_SESSION" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "GITLEAKS_JSON" "GRAPHQL_SPEC" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENAPI_SPEC" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "SYSINTERNALS_ACCESSCHK" "SYSINTERNALS_SIGCHECK" "TWISTCLI_SCAN_JSON" "YELP_DETECT_SECRETS_BASELINE" "ZAP_DAST_ZIP"]
--material string Path to material or attestation file
-p, --policy string Policy reference (./my-policy.yaml, https://my-domain.com/my-policy.yaml, chainloop://my-stored-policy) (default "policy.yaml")
--project string Project name to use as engine context for chainloop.* built-ins
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 9 additions & 4 deletions app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ message CraftingSchema {
// Sysinternals sigcheck output in CSV format
// https://learn.microsoft.com/en-us/sysinternals/downloads/sigcheck
SYSINTERNALS_SIGCHECK = 34;
// Sysinternals AccessChk text output https://learn.microsoft.com/en-us/sysinternals/downloads/accesschk
SYSINTERNALS_ACCESSCHK = 35;
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions pkg/attestation/crafter/api/attestation/v1/crafting_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"strings"

v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials/accesschk"
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials/attestation"
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials/jacoco"
materialsjunit "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials/junit"
Expand Down Expand Up @@ -153,6 +154,18 @@ func (m *Attestation_Material) GetEvaluableContent(value string) ([]byte, error)
if err != nil {
return nil, fmt.Errorf("failed to marshal sigcheck report: %w", err)
}
case v1.CraftingSchema_Material_SYSINTERNALS_ACCESSCHK:
// AccessChk emits plain text; project it to JSON so the policy engine,
// which only consumes JSON, can evaluate it. The raw text is preserved
// in the projection's "raw" field for string-matching fallbacks.
report, perr := accesschk.Parse(rawMaterial)
if perr != nil {
return nil, fmt.Errorf("invalid accesschk material: %w", perr)
}
rawMaterial, err = json.Marshal(report)
if err != nil {
return nil, fmt.Errorf("failed to marshal accesschk material: %w", err)
}
}

// if raw material is empty (container images, for example), let's create an empty json
Expand Down
14 changes: 14 additions & 0 deletions pkg/attestation/crafter/api/attestation/v1/crafting_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,20 @@ func TestGetEvaluableContentWithMetadata(t *testing.T) {
filename: "testdata/sigcheck-report.csv",
testField: "elements",
},
{
name: "accesschk text material projected to json",
material: &Attestation_Material{
MaterialType: schemaapi.CraftingSchema_Material_SYSINTERNALS_ACCESSCHK,
M: &Attestation_Material_Artifact_{
Artifact: &Attestation_Material_Artifact{
Name: "name", Digest: "sha256:deadbeef", IsSubject: true,
Content: []byte("c:\\windows\\system32\\notepad.exe\n RW BUILTIN\\Administrators\n"),
},
},
InlineCas: true,
},
testField: "objects",
},
}

for _, tc := range cases {
Expand Down
83 changes: 83 additions & 0 deletions pkg/attestation/crafter/materials/accesschk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// Copyright 2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package materials

import (
"context"
"fmt"
"os"

schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1"
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials/accesschk"
"github.com/chainloop-dev/chainloop/pkg/casclient"
"github.com/rs/zerolog"
)

// AccessChkCrafter stores the text output of the Sysinternals AccessChk tool as
// supply-chain evidence. The raw text is stored as-is; the text-to-JSON
// projection used by the policy engine happens later, at evaluation time.
type AccessChkCrafter struct {
*crafterCommon
backend *casclient.CASBackend
}

func NewAccessChkCrafter(schema *schemaapi.CraftingSchema_Material, backend *casclient.CASBackend, l *zerolog.Logger) (*AccessChkCrafter, error) {
if schema.Type != schemaapi.CraftingSchema_Material_SYSINTERNALS_ACCESSCHK {
return nil, fmt.Errorf("material type is not an accesschk output")
}
craftCommon := &crafterCommon{logger: l, input: schema}
return &AccessChkCrafter{backend: backend, crafterCommon: craftCommon}, nil
}

func (i *AccessChkCrafter) Craft(ctx context.Context, filePath string) (*api.Attestation_Material, error) {
data, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("can't open the file: %w", err)
}

// Soft fingerprint: AccessChk emits free-form text, so we only require that
// the input is valid text that resembles AccessChk output (a banner, at
// least one access entry, or an SDDL/descriptor marker). The raw text is
// stored unchanged; it is projected to JSON later for policy evaluation.
report, err := accesschk.Parse(data)
if err != nil {
return nil, fmt.Errorf("invalid accesschk output: %w", ErrInvalidMaterialType)
}

if !report.LooksLikeAccessChk() {
return nil, fmt.Errorf("input does not look like accesschk output: %w", ErrInvalidMaterialType)
}

m, err := uploadAndCraft(ctx, i.input, i.backend, filePath, i.logger)
if err != nil {
return nil, err
}

i.injectAnnotations(m, report)

return m, nil
}

func (i *AccessChkCrafter) injectAnnotations(m *api.Attestation_Material, report *accesschk.Report) {
if m.Annotations == nil {
m.Annotations = make(map[string]string)
}
m.Annotations[AnnotationToolNameKey] = report.Tool.Name
if report.Tool.Version != "" {
m.Annotations[AnnotationToolVersionKey] = report.Tool.Version
}
}
Loading
Loading