Skip to content

Build the whole flow from public sources via Dagger - #2

Open
MDr164 wants to merge 4 commits into
mainfrom
chore/release-followups
Open

MDr164 wants to merge 4 commits into
mainfrom
chore/release-followups

Conversation

@MDr164

@MDr164 MDr164 commented Sep 22, 2026

Copy link
Copy Markdown
Member
  • Dagger fetches the TamaGo fork, the shared Kyanite modules and the tamago-go 1.27 release from public GitHub, so dagger call build/test/ci run with no local checkouts (override any --<dir> for local work).
  • Drop the Makefile; Dagger is the single build interface. The A2 host pipeline (tools/a2/gen-a2-image.sh) is now self-contained.
  • Add a CI workflow running dagger call ci on push/PR (SHA-pinned actions, read-only token).
  • Verified end to end: dagger call ci reports build ok / test ok.

Both AST2700 steppings are now driven the same way and write
out/cairn_ast2700_<silicon>.bin: A1 through the reproducible in-container Dagger
pipeline, A2 through tools/a2/gen-a2-image.sh (which needs the vendor
cptra_imgtool signer that is not reproducible in-container). The differing
inputs are exposed as make variables.

Also rename the Dagger image parameter aspeedMcuRuntime -> mcuRuntime (flag
--mcu-runtime) and the a2 script's MCU_RUNTIME_SRC default to ../mcu-runtime so
they match the renamed repository slug.

Signed-off-by: Marvin Drees <marvin.drees@9elements.com>
Make every module/fork source input optional, defaulting to its
kyanitecomputer repository (tamago@aspeed, core, scree, aspeed-go, schema,
aspeed-rs, aspeed-data, mcu-runtime; nats-server@fs-interface; lneto from
soypat at the pinned commit), and install the TamaGo compiler in-container from
the upstream tamago-go 1.27.1 release instead of a passed-in directory. Drops
the --tamago-go input. 'dagger call build/test/ci' now run with no local
checkouts; override any --<dir> for local development. Verified end to end:
dagger call ci reports build ok / test ok.

Signed-off-by: Marvin Drees <marvin.drees@9elements.com>
Run 'dagger call ci' on push and pull_request to main with a read-only
token; both third-party actions are pinned to full commit SHAs. Add a Dependabot
config to keep those action pins current.

Signed-off-by: Marvin Drees <marvin.drees@9elements.com>
Inline the CA35 payload cross-compile into tools/a2/gen-a2-image.sh so the
A2 host pipeline no longer depends on the (now removed) Makefile, and point the
README/AGENTS build instructions at the Dagger interface.

Signed-off-by: Marvin Drees <marvin.drees@9elements.com>
@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

📝 Walkthrough

Walkthrough

The PR moves Cairn builds from Make and local compiler checkouts to Dagger with public source defaults and an in-container TamaGo compiler. It updates A1 and A2 image flows, adds GitHub Actions CI, and revises build documentation.

Changes

Public-source Dagger build flow

Layer / File(s) Summary
Source resolution and compiler setup
.dagger/main.go
Dagger now resolves missing source directories from public repositories, downloads the tamago-go1.27.1 release tarball, installs it under /usr/local/tamago-go, and updates PATH.
Build and test entry points
.dagger/main.go
Build, Test, and Ci no longer accept tamagoGo. Their source directory parameters are optional.
A1 image and BootMCU source fallback
.dagger/main.go
Image requires bmcPb, uses mcuRuntime, defaults the output to cairn_ast2700_a1.bin, and resolves missing BootMCU sources from public repositories.
A2 image, CI, and documentation integration
tools/a2/gen-a2-image.sh, tools/a2/cairn-a2-manifest.toml, .github/workflows/ci.yml, dagger.json, AGENTS.md, Makefile, README.md
The A2 script builds payloads directly with TamaGo. GitHub Actions invokes Dagger CI. Documentation switches to Dagger commands, and the Makefile is removed.

Priority: ⬇️ Low

Merge Risk: 🟡 Moderate · up to a6ca0

Builds are not reproducible and fail on ARM64 hosts, while the CI workflow exposes a read-only credential. Resolve these issues before merging.

Warning

Some tools did not complete. Review the errors below.

🔧 golangci-lint (2.13.2)

level=error msg="Running error: context loading failed: failed to load packages: failed to load packages: failed to load with go/packages: err: exit status 1: stderr: go: cannot load module ../../aspeed-go listed in go.work file: open ../../aspeed-go/go.mod: no such file or directory\ngo: cannot load module ../../core listed in go.work file: open ../../core/go.mod: no such file or directory\ngo: cannot load module ../../lneto listed in go.work file: open ../../lneto/go.mod: no such file or directory\ngo: cannot load module ../../nats-server listed in go.work file: open ../../nats-server/go.mod: no such file or directory\ngo: cannot load module ../../schema/gen/go listed in go.work file: open ../../schema/gen/go/go.mod: no such file or directory\ngo: cannot load module ../../scree listed in go.work file: open ../../scree/go.mod: no such file or directory\ngo: cannot load module ../../tamago listed in go.work file: open ../../tamago/go.mod: no such file or directory\n"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: ac34fc62-3956-467e-964e-e0006f750ca4

📥 Commits

Reviewing files that changed from the base of the PR and between 894d702 and a6ca078.

📒 Files selected for processing (8)
  • .dagger/main.go
  • .github/workflows/ci.yml
  • AGENTS.md
  • Makefile
  • README.md
  • dagger.json
  • tools/a2/cairn-a2-manifest.toml
  • tools/a2/gen-a2-image.sh
💤 Files with no reviewable changes (1)
  • Makefile

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread .dagger/main.go
// it is fetched from the kyanitecomputer GitHub org so the whole flow builds
// from public sources with no local checkouts. Override any --<dir> for
// local development.
orgGit = "https://github.com/kyanitecomputer/"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '55,165p' .dagger/main.go
rg -n 'linux-amd64|Platform|WithPlatform|amd64|arm64|architecture|Dagger' .dagger dagger.json README.md AGENTS.md .github

Repository: kyanitecomputer/cairn

Length of output: 7020


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- focused main.go ---'
sed -n '1,275p' .dagger/main.go
printf '%s\n' '--- dagger and CI configuration ---'
for f in dagger.json .github/workflows/ci.yml AGENTS.md README.md; do
  if [ -f "$f" ]; then
    printf '\n--- %s ---\n' "$f"
    cat -n "$f"
  fi
done
printf '%s\n' '--- platform and architecture references ---'
rg -n -i 'platform|linux/|amd64|arm64|architecture|arch|runner|runs-on|dagger' --glob '!*.sum' --glob '!vendor/**' .dagger .github dagger.json AGENTS.md README.md 2>/dev/null || true
printf '%s\n' '--- all Container constructors and platform calls ---'
rg -n 'dag\.Container|WithPlatform|Platform\(' . --glob '!*.sum' --glob '!vendor/**' 2>/dev/null || true

Repository: kyanitecomputer/cairn

Length of output: 28090


🌐 Web query:

Dagger Go SDK Container default platform dag.Container explicit WithPlatform host default official documentation

💡 Result:

<source_evidence>

<title>core/schema/container.go</title> https://github.com/dagger/dagger/blob/c7d72b6c/core/schema/container.go func (s *containerSchema) Install(srv *dagql.Server) { dagql.Fields[*core.Query]{ dagql.Func("container", s.container). Doc(`Creates a scratch container, with no image or metadata.`, `To pull an image, follow up with the "from" function.`). Args( dagql.Arg("platform").Doc(`Platform to initialize the container with. Defaults to the native platform of the current engine`), ), }.Install(srv) dagql.Fields[*core.Container]{ Syncer*core.Container. Doc(`Forces evaluation of the pipeline in the engine.`, `It doesn&`#39`;t run the default command if no exec has been set.`), dagql.Func("pipeline", s.pipeline). View(BeforeVersion("v0.13.0")). Deprecated("Explicit pipeline creation is now a no-op"). Doc(`Creates a named sub-pipeline.`). Args( dagql.Arg("name").Doc("Name of the sub-pipeline."), dagql.Arg("description").Doc("Description of the sub-pipeline."), dagql.Arg("labels").Doc("Labels to apply to the sub-pipeline."), ), dagql.NodeFunc("from", s.from). IsPersistable(). WithInput(fromSessionScopeInput). Doc(`Download a container image, and apply it to the container state. All previous state will be lost.`). Args( dagql.Arg("address").Doc( `Address of the container image to download, in standard OCI ref format. Example:"registry.dagger.io/engine:latest"`, ), dagql.Arg("registryService").Doc( `Service to use as the registry endpoint for the image address.`, `The service will be started only for this pull.`). View(AfterVersion("v0.21.6")), dagql.Arg("protocol").Doc( `Protocol to use for registry communication.`, `Defaults to "HTTPS". Use "HTTP" only for plain HTTP registries.`). View(AfterVersion("v1.0.0-0")), dagql.Arg("insecureSkipTLSVerify").Doc(`Allow HTTPS registry communication without verifying the server certificate.`). View(AfterVersion("v1.0.0-0")), ), dagql.NodeFunc("build", s.build). View(BeforeVersion("v0.19.0")). Deprecated("Use `Directory.build` instead"). Doc(`Initializes this container from a Dockerfile build.`). Args( dagql.Arg("context").Doc("Directory context used by the Dockerfile."), dagql.Arg("dockerfile").Doc("Path to the Dockerfile to use."), dagql.Arg("target ... Doc("Target build stage to build."), dagql.Arg("buildArgs").Doc("Additional build arguments."), dagql.Arg("secrets").Doc(`Secrets to pass to the build.`, `They will be mounted at ... run/secrets/[secret-name] in the build container`, `They can be accessed in the Dockerfile using the "secret" mount type ... and mount path /run/secrets ... secret-name], e.g. RUN --mount=type=secret,id=my-secret curl http://example.com?token=$(cat /run/secrets/my-secret)`), dagql.Arg("noInit").Doc(`If set, skip the automatic init process ... into containers created by ... statements.`, `This should only be used if the user requires that their exec processes be the pid 1 process in the container. Otherwise it may result in unexpected behavior.`, ), ), dagql.NodeFunc("rootfs", s.rootfs). ... a snapshot of the container&`#39`;s root filesystem. The snapshot can be modified ... back using withRootfs. Use that method for filesystem modifications.`), dagql.NodeFunc("withRootfs", s.withRootfs). Doc(`Change the container&`#39`;s root filesystem. The previous root filesystem will be lost.`). Args( dagql.Arg("directory").Doc("The new root filesystem."), ... ), dagql.NodeFunc("directory", ... .directory). ... (`Retrieve a directory from the container&`#39`;s ... `Mounts are ... .`). Args( dagql.Arg("path").Doc(`The path of the directory to retrieve (e.g., "./src").`), dagql.Arg("expand").Doc(`Replace "${VAR}" or "$VAR" in th…[truncated] <title>Multiplatform support</title> GitHub pull request 3119 in dagger/dagger (link omitted to avoid creating a cross-reference) withPlatforms`, ... > ```graphql > scalar Platform # TODO: maybe this will be a good use for enum? Though then we need to update the enum when new CPU arches and OSes drop (that&`#39`;s probably fine) > > type Directory { > "the platform associated with the Directory (or null if it&`#39`;s platform agnostic)" > platform: Platform > > "force the Directory to be associated with a different platform (needed primarily for cross-compilation use cases)" > withForcedPlatform(platform: Platform): Directory! > } > > type File { > "the platform associated with the File (or null if it&`#39`;s platform agnostic)" > platform: Platform > > "force the File to be associated with a different platform (needed primarily for cross-compilation use cases)" > withForcedPlatform(platform: Platform): File! > } > > type Container { > "The default platform for Directories in this container (if directories don&`#39`;t have explicitly forced platforms)" > withPlatform(platform: Platform): Directory! > > "same publish as before, but now with the option to include extra Containers that will be used as different platform variants in a multi-arch image." > publish(address: String!, platformVariants: [ContainerID!]) String > } > ``` > > Platform will default to that of the Buildkit host, not of the client ... > * It&`#39`;s not obvious which is the right choice here, so I&`#39`;m breaking the tie by the fact that builds will be faster by default (no emulation needed) if they default to the Buildkit host platform. > * The price we pay is that users who want to export binaries to their local filesystem and execute them will need to explicitly set the platform. I suspect this not the majority of use cases though, so I think it&`#39`;s the right sacrifice. ... > ```go > var variants []dagger.ContainerID > for _, plat := range []string{"linux/amd64", "linux/arm64"} { > builtDir, _ := c.Container(). > WithPlatform(plat). > From("builder:latest"). > WithMountedDirectory("/src", srcDir). > WithMountedDirectory("/output", c.Directory()). > Exec("build /src -o /output"). > Directory("/output") > > variants = append(variants, c.Container().WithFS(builtDir).ID()) > } > > c.Container().Publish("my-multiarch-image:latest", variants) > ``` ... > ... more, I suspect ... actually possible to accomplish all ... withPlatform` is on `Container ... rather than `Query`. The ... us, as ... agger devs) is more complicated, but the UX is at least the same and likely better due to the aforementioned issues ... > After writing the implementation more I&`#39`;m resigning to an even simpler design. > > The problem with the previous one described in this older comment is that supporting `WithPlatform` implies either: > > #### A) The new platform only applies going forward for new steps in the container > > This just isn&`#39`;t very useful and is confusing. E.g. this would either fail or output the original default platform > ```go > c.Container().From("alpine").WithPlatform("non-default-platform").Exec("uname -a").Stdout() > ``` ... > > #### B) The platform specified applies "retroactively" to the LLB DAG of the previous container steps. > > E.g. the following would result in the output of "non-default-platform" even though the non default platform was specified after you pulled the image and defined the exec: ... > ```go > c.Container().From("alpine").Exec("uname -a").WithPlatform("non-default-platform").Stdout() > ``` ... > This is more intuitive in some ways but ends up being an ... hole for a number of ... reasons both in terms of UX and implementation details ... Mostly things in…[truncated] <title>Contextual platform · Issue `#8354` · dagger/dagger</title> GitHub issue 8354 in dagger/dagger (link omitted to avoid creating a cross-reference) A Dagger function can&`#39`;t access its client&`#39`;s platform (OS and arch). As a result, the only way to build a native binary for the client, is to request mandatory arguments. ... Apply the same "context access" model as for contextual directories: ... - Function can receive a `dagger.Platform` as argument - If the argument is optional, the default value is the client&`#39`;s platform ... ```golang func (m *MyModule) Build( ctx context.Context, source *dagger.Directory // +optional platform *dagger.Platform, ) { // We assume there is a reusable go module that handles platform return dag.Go.Build(source, platform) } ``` ... > > [...] the only way to build a native binary for the client, is to request mandatory arguments. > > The CLI supports a simpler way since https://github.com/dagger/dagger/pull/7158 by using the value "current": > > ```console > dagger call native-build --platform=current -o . > ``` ... > resurfacing this issue with more context from `@shykes` in discord here: > > https://discord.com/channels/707636530424053791/1285660387433513114/1285671835454673040 > > > I know 🙂 I want a way to choose between them in my pragmas > > Proposal: > > ```go > func Foo( > // +optional > // +default="client" <-- OPTION 1: use the client&`#39`;s platform > // +default="runtime" <-- OPTION 2: use the current runtime platform (running the current function) > // +default="engine" <-- OPTION 3: use the engine&`#39`;s default platform > platform dagger.Platform, > ) > ``` > > > The CLI would map them: > > > $ dagger call foo --platform=client > > $ dagger call foo --platform=runtime > > $ dagger call foo --platform=engine > > > "runtime" and "engine" may not be the same if eg. the engine supports multiple platforms, and the current function is running in a non-default one, in qemu for some reason. I don&`#39`;t think there&`#39`;s a way to configure this at the moment, but it could conceivably happen ... > Needed this today. Intuitively, I tried to use `dag.Module().Runtime().Platform(ctx)` but that panic&`#39`;ed. > > A use case for me would be building a docker image for the architecture of the host platform. Need a way to detect host platform or engine runtime platform which would in most cases match host platform. ... > There&`#39`;s one important thing I want to resolve - this propagates *all the way back to the client*? I just wonder if that&`#39`;s the behavior we want, or whether we just want the platform of the caller. > > So if I go `dagger shell` calls module `A` calls module `B` calls module `C` - module `C` is able to access the client platform, i.e. `dagger shell`? This is a bit of a departure from how we call and cache things today: today the only thing that matters is `B` calling `C`. If we allow this, it would be the first "global variable" we&`#39`;d have. Note: context directories are different than this, because they read from the *called* module context, not the *caller*. > > Personally, I think we should allow these special values for now (don&`#39`;t really care about the names): > > - `current`/`client` > > - the platform that the module is actually running in > > - `caller`/`engine`/`runtime` > > - the platform that the caller is running: if called from the cli/`shell`, it&`#39`;s the platform of the cli, if it&`#39`;s called from another module, then it&`#39`;s actually the same as `current` (but because it&`#39`;s being called from code, then the code should manually propagate the value here). > > That said, maybe there is value in a `client` value that&`#39`;s from the top-most level? We just need to be careful that caching keeps working properly (and that we don&`#39`;t cache calls to the hypothetical module `C` when the client platform is different). > > Could we…[truncated] <title>core/integration/platform_test.go</title> https://github.com/dagger/dagger/blob/c7d72b6c/core/integration/platform_test.go func (PlatformSuite) TestEmulatedExecAndPush(ctx context.Context, t *testctx.T) { c := connect(ctx, t) variants := make([]*dagger.Container, 0, len(platformToUname)) for platform, uname := range platformToUname { ctr := c.Container(dagger.ContainerOpts{Platform: platform}). From(alpineImage). WithExec([]string{"uname", "-m"}) variants = append(variants, ctr) ctrPlatform, err := ctr.Platform(ctx) require.NoError(t, err) require.Equal(t, platform, ctrPlatform) output, err := ctr.Stdout(ctx) require.NoError(t, err) output = strings.TrimSpace(output) require.Equal(t, uname, output) } testRef := registryRef("platform-emulated-exec-and-push") _, err := c.Container().Publish(ctx, testRef, dagger.ContainerPublishOpts{ PlatformVariants: variants, }) ... require.NoError(t, err) ... for platform, uname := ... ctr := c.Container(dagger.ContainerOpts{Platform: platform}). ... ([]string{"uname", ... m"}) output, err := ctr.Stdout(ctx) require.NoError(t, err) output = strings.TrimSpace(output) require.Equal(t, uname, output) } ... const registry ... 5000 ... WithMountedCache ... lib/registry", ... CacheVolume("platform-single-tag-registry-"+identity.NewID())). ... WithExposed ... 5000 ... ContainerWithExposed ... dagger.NetworkProtocolTcp ... AsService() ... startFreshEngine ... ctx context.Context ... *testctx.T ... , func()) ... , err) ... linux/amd6 ... Engine(ctx, t) ... _, platform := range platforms { marker ... from " + string(platform) ref := registryHost + "/platform-single-tag-" + strings.ReplaceAll(string(platform), "/", "-") + ":" + identity ... NewID() _, err := pushClient.Container(dagger.ContainerOpts{Platform: platform}). WithRootfs(pushClient.Directory(). ... NewFile("platform.txt", marker)). Publish(ctx, ref) require ... (t, err) ... := range platforms ... from " + string(platform) variants = append(variants ... Client.Container ... dagger.ContainerOpts ... pushClient.Directory().WithNewFile("platform.txt", ... marker } ... ref := registryHost + "/platform-multi-tag:" + identity.NewID() _, err := pushClient.Container().Publish(ctx, ref, dagger.ContainerPublishOpts{ PlatformVariants: variants, }) require.NoError(t, err) cleanupPushEngine() assertPulled := func(ctx context.Context, t *testctx.T, ctr *dagger.Container, expectedPlatform dagger.Platform) { t.Helper() ctrPlatform, err := ctr.Platform(ctx) require.NoError(t, err) require.Equal(t, expectedPlatform, ctrPlatform) contents, err := ctr.File("/platform.txt").Contents(ctx) require.NoError(t, err) require.Equal(t, markers[expectedPlatform], contents) } t.Run("default platform", func(ctx context.Context, t *testctx.T) { pullClient, cleanupPullEngine := startFreshEngine(ctx, t) defer cleanupPullEngine() defaultPlatform, err := pullClient.DefaultPlatform(ctx) require.NoError(t, err) require.Contains(t, markers, defaultPlatform) assertPulled(ctx, t, pullClient.Container().From(ref), defaultPlatform) }) for _, platform := range platforms { platform := platform t.Run(string(platform), func(ctx context.Context, t *testctx.T) { pullClient, cleanupPullEngine := startFreshEngine(ctx, t) defer cleanupPullEngine() assertPulled(ctx, t, pullClient.Container(dagger.ContainerOpts{Platform: platform}).From(ref), platform) }) } } ... func (PlatformSuite) TestCrossCompile(ctx context.Context, t *testctx.T) { c := connect(ctx, t, dagger.WithWorkdir("../..")) // cross compile the dagger binary for each platform defaultPlatform, err := c.DefaultPlatform(ctx) require.NoError(t, err) variants := make([]*dagger.Container, len(platformToFileArch)) i := 0 var eg errgroup.Group for platform := range platformToUname { i++ i := i - 1 eg.Go(func() error { ctr := c.Container(). From("crazymax/goxx:latest"). WithMountedCache("/go/pkg/mod", c.CacheVolume("gomod")). WithMountedCache("/root/.ca…[truncated] <title>core/container.go</title> https://github.com/dagger/dagger/blob/0e9fd00b/core/container.go // Container is a content-addressed container. type Container struct { // The container&`#39`;s root filesystem. FS *LazyAccessor[*Directory, *Container] // Image configuration (env, workdir, etc) Config dockerspec.DockerOCIImageConfig // List of GPU devices that will be exposed to the container EnabledGPUs []string // Mount points configured for the container. Mounts ContainerMounts // MetaSnapshot is the internal exec metadata snapshot containing stdout, // stderr, combined output, and exit code files. It will be nil if nothing // has run yet. MetaSnapshot *LazyAccessor[bkcache.ImmutableRef, *Container] // The platform of the container&`#39`;s rootfs. Platform Platform // OCI annotations Annotations []containerutil.ContainerAnnotation // Secrets to expose to the container. Secrets []ContainerSecret ... // Services to start before running the container. Services ServiceBindings // The args to invoke when using the terminal api on this container. DefaultTerminalCmd DefaultTerminalCmdOpts // (Internal-only for now) Environment variables from the engine container, prefixed // with a special value, that will be inherited by this container if set. SystemEnvNames []string // DefaultArgs have been explicitly set by the user DefaultArgs bool Lazy Lazy[*Container] } ... func NewContainer(platform Platform) *Container { return &Container{ FS: new(LazyAccessor[*Directory, *Container]), MetaSnapshot: new(LazyAccessor[bkcache.ImmutableRef, *Container]), Platform: platform, } } ... DefaultTerminalCmd ... func (lazy *ContainerWithEntrypointLazy) Evaluate(ctx context.Context, container *Container) error { return lazy.LazyState.Evaluate(ctx, "Container.withEntrypoint", func(ctx context ... Context) error { if err := materializeContainerStateFromParent(ctx, container, lazy.Parent); err != nil { return err } _, err := container.UpdateImageConfig(ctx, func(cfg dockerspec.DockerOCIImageConfig) dockerspec.DockerOCIImageConfig { cfg.Entrypoint = slices.Clone(lazy.Args) if !lazy.KeepDefaultArgs { cfg.Cmd = nil } return cfg }) if err != nil { return err } if !lazy ... KeepDefaultArgs { container.DefaultArgs = false } container.Lazy = nil return nil }) } ... func (lazy *ContainerWithDefaultArgsLazy) Evaluate(ctx context.Context, container *Container) error { return lazy.LazyState.Evaluate(ctx, "Container.withDefaultArgs", func(ctx context.Context) error { if err := materializeContainerStateFromParent(ctx, container, lazy.Parent); err != nil { return err } container.DefaultArgs = true _, err := container.UpdateImageConfig(ctx, func(cfg dockerspec.DockerOCIImageConfig) dockerspec.DockerOCIImageConfig { if lazy.Args == nil { cfg.Cmd = []string{} } else { cfg.Cmd = slices.Clone(lazy.Args) } return cfg }) if err != nil { return err } container.Lazy = nil return nil }) }

Citations:


🌐 Web query:

Dagger v0.21.9 Go SDK ContainerOpts Platform default native platform current engine official source

💡 Result:

<source_evidence>

<title>core/schema/container.go</title> https://github.com/dagger/dagger/blob/0e9fd00b/core/schema/container.go func (s *containerSchema) Install(srv *dagql.Server) { dagql.Fields[*core.Query]{ dagql.Func("container", s.container). Doc(`Creates a scratch container, with no image or metadata.`, `To pull an image, follow up with the "from" function.`). Args( dagql.Arg("platform").Doc(`Platform to initialize the container with. Defaults to the native platform of the current engine`), ), }.Install(srv) dagql.Fields[*core.Container]{ Syncer*core.Container. Doc(`Forces evaluation of the pipeline in the engine.`, `It doesn&`#39`;t run the default command if no exec has been set.`), dagql.Func("pipeline", s.pipeline). View(BeforeVersion("v0.13.0")). Deprecated("Explicit pipeline creation is now a no-op"). Doc(`Creates a named sub-pipeline.`). Args( dagql.Arg("name").Doc("Name of the sub-pipeline."), dagql.Arg("description").Doc("Description of the sub-pipeline."), dagql.Arg("labels").Doc("Labels to apply to the sub-pipeline."), ), dagql.NodeFunc("from", s.from). IsPersistable(). WithInput(fromSessionScopeInput). Doc(`Download a container image, and apply it to the container state. All previous state will be lost.`). Args( dagql.Arg("address").Doc( `Address of the container image to download, in standard OCI ref format. Example:"registry.dagger.io/engine:latest"`, ), ), dagql.NodeFunc("build", s.build). View(BeforeVersion("v0.19.0")). Deprecated("Use `Directory.build` instead"). Doc(`Initializes this container from a Dockerfile build.`). Args( dagql.Arg("context").Doc("Directory context used by the Dockerfile."), dagql.Arg("dockerfile").Doc("Path to the Dockerfile to use."), dagql.Arg("target").Doc("Target build stage to build."), dagql.Arg("buildArgs").Doc("Additional build arguments."), dagql.Arg("secrets").Doc(`Secrets to pass to the build.`, `They will be mounted at /run/secrets/[secret-name] in the build container`, `They can be accessed in the Dockerfile using the "secret" mount type and mount path /run/secrets/[secret-name], e.g. RUN --mount=type=secret,id=my-secret curl http://example.com?token=$(cat /run/secrets/my-secret)` ... ql.Arg(" ... ").Doc(`If set ... .`, `This should only be used if the user requires that their exec processes be the pid 1 process ... the container. Otherwise it may result in ... .`, ), ... Func("rootfs", ... .rootfs). ... (`Return a snapshot of the ... &`#39`;s root filesystem. The snapshot can be modified then written back using withRootfs. Use that method for filesystem modifications.`), dagql.NodeFunc("withRootfs", s.withRootfs). Doc(`Change the container&`#39`;s root filesystem. The previous root filesystem ... be lost.`). Args( dagql.Arg("directory").Doc("The new root filesystem."), ), dagql.NodeFunc("directory", ... .directory). ... Doc(`Retrieve a directory from the container&`#39`;s ... .`). Args( dagql.Arg("path").Doc(`The path of the directory to retrieve (e.g., "./src").`), dagql.Arg("expand").Doc(`Replace "${VAR}" or "$VAR" in the value of path according to the current ` ... variables defined in the container (e.g. "/$VAR/foo").`), ), dagql.NodeFunc("file", s.file). Doc(`Retrieves a file at the given path.`, ... .`). ... ql.Arg(" ... of the file ... .g., ... README.md").`), dagql.Arg(" ... "$VAR" in the value of path ... the current ` ... `environment variables defined in ... container (e.g. ... /foo.txt").`), ... Func("user", ... Doc("Retrieves the user ... set for all ... ."), dagql.NodeFunc("withUser", s.withUser). Doc(`Retrieves this container with a different command user.`). Args( dagql.Arg("name").Doc(`The user to set…[truncated] <title>core/schema/container.go</title> https://github.com/dagger/dagger/blob/v0.21.7/core/schema/container.go func (s *containerSchema) Install(srv *dagql.Server) { dagql.Fields[*core.Query]{ dagql.Func("container", s.container). Doc(`Creates a scratch container, with no image or metadata.`, `To pull an image, follow up with the "from" function.`). Args( dagql.Arg("platform").Doc(`Platform to initialize the container with. Defaults to the native platform of the current engine`), ), }.Install(srv) dagql.Fields[*core.Container]{ Syncer*core.Container. Doc(`Forces evaluation of the pipeline in the engine.`, `It doesn&`#39`;t run the default command if no exec has been set.`), dagql.Func("pipeline", s.pipeline). View(BeforeVersion("v0.13.0")). Deprecated("Explicit pipeline creation is now a no-op"). Doc(`Creates a named sub-pipeline.`). Args( dagql.Arg("name").Doc("Name of the sub-pipeline."), dagql.Arg("description").Doc("Description of the sub-pipeline."), dagql.Arg("labels").Doc("Labels to apply to the sub-pipeline."), ), dagql.NodeFunc("from", s.from). IsPersistable(). WithInput(fromSessionScopeInput). Doc(`Download a container image, and apply it to the container state. All previous state will be lost.`). Args( dagql.Arg("address").Doc( `Address of the container image to download, in standard OCI ref format. Example:"registry.dagger.io/engine:latest"`, ), dagql.Arg("registryService").Doc( `Service to use as the registry endpoint for the image address.`, `The service will be started only for this pull.`), ), dagql.NodeFunc("build", s.build). View(BeforeVersion("v0.19.0")). Deprecated("Use `Directory.build` instead"). Doc(`Initializes this container from a Dockerfile build.`). Args( dagql.Arg("context").Doc("Directory context used by the Dockerfile."), dagql.Arg("dockerfile").Doc("Path to the Dockerfile to use."), dagql.Arg("target").Doc("Target build stage to build."), dagql.Arg("buildArgs").Doc("Additional build arguments."), dagql.Arg("secrets").Doc(`Secrets to pass to the build.`, `They will be mounted at /run/secrets/[secret-name] in the build container`, `They can be accessed in the Dockerfile using the "secret" mount type and mount ... /run/secrets ... secret-name], e.g. RUN --mount ... the user requires that their ... .`, ... Func("rootfs", ... .rootfs ... (`Return a snapshot of the ... &`#39`;s root filesystem. The snapshot ... written back using withRootfs. Use that method for filesystem modifications.`), dagql.NodeFunc("withRootfs", s.withRootfs). Doc(`Change the ... &`#39`;s root filesystem ... The previous root filesystem ... be lost.`). Args( ... dagql. ... ("directory").Doc("The new root filesystem."), ), dagql.NodeFunc("directory", ... .directory). Doc(`Retrieve a directory from the container&`#39`;s root filesystem`, ... .`). Args( ... ql.Arg("path").Doc(`The path of the directory ... e.g., "./src").`), dagql.Arg("expand").Doc(`Replace "${VAR}" or "$VAR" in the value of path according to the current ` ... `environment variables defined in the container (e.g. "/$VAR/foo").`), ), dagql.NodeFunc("file", s.file). Doc(`Retrieves a file ... the given path.`, ... .`). ... .g. ... foo.txt ... Func("user ... ves the user ... Func("withUser", ... .withUser). ... ves this container with a ... command user.`). Args( dagql.Arg("name ... .g., "root").`), ), ... .withoutUser). ... Func("workdir ... workdir). ... ", s.withWorkdir). ... the working directory ... dagql.NodeFunc(" ... Doc(` ... of persistent environment variables ... the container.`), dagql.NodeFunc(" ... .envVariable ... Doc(`Retrieves the ... persistent environment variable.`). Args( ... ql.Arg(" ... the envi…[truncated] <title>Result 3</title> https://context7.com/websites/pkg_go_dev_dagger_io_dagger/llms.txt?tokens=10000 ### ContainerOpts for Container Initialization ... Source: https://pkg.go.dev/dagger.io/dagger ... Options for initializing a new container. Allows specifying the platform for the container. ... ```go type ContainerOpts struct { // Platform to initialize the container with. Defaults to the native platform of the current engine Platform Platform } ``` ... ### Query.Container Options ... Source: https://pkg.go.dev/dagger.io/dagger ... Options for initializing a new container, primarily for setting the platform. ... ```APIDOC ## Query.Container Options ... Options for initializing a new container. The primary option is to specify the desired platform for the container. ... `ContainerOpts` ... - **Platform** (`Platform`) - Platform to initialize the container with. Defaults to the native platform of the current engine. <title>Contextual platform · Issue `#8354` · dagger/dagger</title> GitHub issue 8354 in dagger/dagger (link omitted to avoid creating a cross-reference) A Dagger function can&`#39`;t access its client&`#39`;s platform (OS and arch). As a result, the only way to build a native binary for the client, is to request mandatory arguments. ... Apply the same "context access" model as for contextual directories: ... - Function can receive a `dagger.Platform` as argument - If the argument is optional, the default value is the client&`#39`;s platform ... ```golang func (m *MyModule) Build( ctx context.Context, source *dagger.Directory // +optional platform *dagger.Platform, ) { // We assume there is a reusable go module that handles platform return dag.Go.Build(source, platform) } ``` ... > > [...] the only way to build a native binary for the client, is to request mandatory arguments. > > The CLI supports a simpler way since https://github.com/dagger/dagger/pull/7158 by using the value "current": > > ```console > dagger call native-build --platform=current -o . > ``` ... > resurfacing this issue with more context from `@shykes` in discord here: > > https://discord.com/channels/707636530424053791/1285660387433513114/1285671835454673040 > > > I know 🙂 I want a way to choose between them in my pragmas > > Proposal: > > ```go > func Foo( > // +optional > // +default="client" <-- OPTION 1: use the client&`#39`;s platform > // +default="runtime" <-- OPTION 2: use the current runtime platform (running the current function) > // +default="engine" <-- OPTION 3: use the engine&`#39`;s default platform > platform dagger.Platform, > ) > ``` > > > The CLI would map them: > > > $ dagger call foo --platform=client > > $ dagger call foo --platform=runtime > > $ dagger call foo --platform=engine > > > "runtime" and "engine" may not be the same if eg. the engine supports multiple platforms, and the current function is running in a non-default one, in qemu for some reason. I don&`#39`;t think there&`#39`;s a way to configure this at the moment, but it could conceivably happen ... > Needed this today. Intuitively, I tried to use `dag.Module().Runtime().Platform(ctx)` but that panic&`#39`;ed. > > A use case for me would be building a docker image for the architecture of the host platform. Need a way to detect host platform or engine runtime platform which would in most cases match host platform. ... > There&`#39`;s one important thing I want to resolve - this propagates *all the way back to the client*? I just wonder if that&`#39`;s the behavior we want, or whether we just want the platform of the caller. > > So if I go `dagger shell` calls module `A` calls module `B` calls module `C` - module `C` is able to access the client platform, i.e. `dagger shell`? This is a bit of a departure from how we call and cache things today: today the only thing that matters is `B` calling `C`. If we allow this, it would be the first "global variable" we&`#39`;d have. Note: context directories are different than this, because they read from the *called* module context, not the *caller*. > > Personally, I think we should allow these special values for now (don&`#39`;t really care about the names): > > - `current`/`client` > > - the platform that the module is actually running in > > - `caller`/`engine`/`runtime` > > - the platform that the caller is running: if called from the cli/`shell`, it&`#39`;s the platform of the cli, if it&`#39`;s called from another module, then it&`#39`;s actually the same as `current` (but because it&`#39`;s being called from code, then the code should manually propagate the value here). > > That said, maybe there is value in a `client` value that&`#39`;s from the top-most level? We just need to be careful that caching keeps working properly (and that we don&`#39`;t cache calls to the hypothetical module `C` when the client platform is different). > > Could we…[truncated] <title>Stop Writing YAML: Build Your CI/CD Pipelines in Real Code with Dagger | by Damini Bansal | Jun, 2026 | Medium</title> https://daminibansal.medium.com/stop-writing-yaml-build-your-ci-cd-pipelines-in-real-code-with-dagger-7e093ebdfad4 The concept: instead of writing YAML, you write your pipeline in a real programming language — Go, Python, TypeScript, Java, PHP, Rust, or Elixir — using the Dagger SDK. The SDK talks to the Dagger Engine, which runs everything inside standard OCI containers. Because it’s containers all the way down, the pipeline runs identically on your laptop, in GitHub Actions, in GitLab CI, or anywhere Docker runs. ... ``` package mainimport ( "context" "fmt" "os" "dagger.io/dagger")func main() { ctx := context.Background() // Connect to the Dagger engine client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stdout)) if err != nil { panic(err) } defer client.Close() // Load source code, excluding things the build doesn&`#39`;t need src := client.Host().Directory(".", dagger.HostDirectoryOpts{ Exclude: []string{".git", "ci/", "*.md"}, }) // Persistent caches for Go modules and build artifacts goModCache := client.CacheVolume("go-mod-cache") goBuildCache := client.CacheVolume("go-build-cache") // Base container with source code and caches mounted golang := client.Container(). From("golang:1.24-alpine"). WithMountedCache("/go/pkg/mod", goModCache). WithMountedCache("/root/.cache/go-build", goBuildCache). WithDirectory("/app", src). WithWorkdir("/app") // Run tests fmt.Println("Running tests...") _, err = golang. WithExec([]string{"go", "test", "-v", "-race", "./..."}). Stdout(ctx) if err != nil { fmt.Println("Tests failed!") os.Exit(1) } // Build a statically-linked binary fmt.Println("Building binary...") builder := golang. WithEnvVariable("CGO_ENABLED", "0"). WithEnvVariable("GOOS", "linux"). WithExec([]string{ "go", "build", "-ldflags", "-s -w", "-o", "/app/bin/server", "./cmd/server", }) // Package into a minimal production image fmt.Println("Building production image...") prodImage := client.Container(). From("gcr.io/distroless/static-debian12"). WithFile("/server", builder.File("/app/bin/server")). WithEntrypoint([]string{"/server"}). WithExposedPort(8080) // Publish ref, err := prodImage.Publish(ctx, "ghcr.io/myorg/myservice:latest") if err != nil { fmt.Println("Publish failed!") os.Exit(1) } fmt.Printf("Published: %s\n", ref)} ``` ... ``` func buildMultiPlatform(ctx context.Context, client *dagger.Client, src *dagger.Directory) error { platforms := []dagger.Platform{"linux/amd64", "linux/arm64"} variants := make([]*dagger.Container, len(platforms)) for i, platform := range platforms { // Build the Go binary targeting this specific platform builder := client.Container(dagger.ContainerOpts{Platform: platform}). From("golang:1.24-alpine"). WithDirectory("/app", src). WithWorkdir("/app"). WithEnvVariable("CGO_ENABLED", "0"). WithExec([]string{ "go", "build", "-ldflags", "-s -w", "-o", "/app/bin/server", "./cmd/server", }) // Package into a distroless image for this platform variants[i] = client.Container(dagger.ContainerOpts{Platform: platform}). From("gcr.io/distroless/static-debian12"). WithFile("/server", builder.File("/app/bin/server")). WithEntrypoint([]string{"/server"}). WithExposedPort(8080) } // Publish as a multi-platform manifest _, err := client.Container(). Publish(ctx, "ghcr.io/myorg/myservice:latest", dagger.ContainerPublishOpts{PlatformVariants: variants}) return err} ``` ... Both the builder and the production container receive the`Platform` option so the Go toolchain cross-compiles correctly, and the final i…[truncated]

Citations:


Run the build container as linux/amd64.

dag.Container() defaults to the native platform of the Dagger engine. On an ARM64 engine, the container can run as ARM64, but the downloaded tamago-go1.27.1.linux-amd64.tar.gz provides an amd64 go executable. The go build and go test steps can then fail with an executable-format error. The project does not restrict Dagger builds to amd64.

Suggested fix
-	return dag.Container().
+	return dag.Container(dagger.ContainerOpts{Platform: "linux/amd64"}).

Comment thread .dagger/main.go
if dir != nil {
return dir
}
return dag.Git(orgGit + repo).Branch(ref).Tree()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '50,165p' .dagger/main.go
rg -n 'reproduc|pin|Branch\(|Commit\(|tamagoRef|natsServerRef|branchMain|go\.work|replace ' .dagger README.md AGENTS.md go.mod go.sum 2>/dev/null
git diff -- .dagger/main.go README.md AGENTS.md go.mod go.sum

Repository: kyanitecomputer/cairn

Length of output: 8639


🏁 Script executed:

#!/bin/bash
set -o pipefail
printf '%s\n' '--- README relevant section ---'
sed -n '65,100p' README.md
printf '%s\n' '--- AGENTS relevant section ---'
sed -n '1,55p' AGENTS.md
printf '%s\n' '--- root go.mod relevant section ---'
sed -n '1,35p' go.mod
printf '%s\n' '--- current Dagger header and source-resolution comments ---'
sed -n '1,125p' .dagger/main.go
printf '%s\n' '--- parent Dagger source-resolution/build wiring ---'
git show HEAD^:.dagger/main.go 2>/dev/null | sed -n '1,145p'
printf '%s\n' '--- concise recent history for relevant files ---'
git log --oneline -8 -- .dagger/main.go README.md AGENTS.md go.mod

Repository: kyanitecomputer/cairn

Length of output: 18342


🏁 Script executed:

#!/bin/bash
set -o pipefail
printf '%s\n' '--- public-source commit summary ---'
git show --stat --oneline --decorate 9824e7f
printf '%s\n' '--- public-source commit message and relevant diff ---'
git show --format=fuller --no-ext-diff 9824e7f -- .dagger/main.go README.md AGENTS.md go.mod | sed -n '1,260p'
printf '%s\n' '--- predecessor Dagger source resolution ---'
git show 9824e7f^:.dagger/main.go 2>/dev/null | sed -n '50,145p'
printf '%s\n' '--- CI references to Dagger entrypoints ---'
rg -n -C 3 'dagger (call|run)|call ci|call build|call test|Dagger' .github Makefile* tools 2>/dev/null || true

Repository: kyanitecomputer/cairn

Length of output: 17486


Pin public fallback sources to immutable commits.

orRepo resolves main, aspeed, and fs-interface with Branch(ref). The default build and CI paths use these fallbacks, so an upstream branch update can change the source tree or make the same Cairn revision fail without a Cairn change. Replace all fallback branch refs, including the image sources, with commit IDs and resolve them with Commit(ref), as the lneto fallback already does.

Comment thread .github/workflows/ci.yml
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🛡️ Detected with Advanced Tier | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1,80p' .github/workflows/ci.yml
sed -n '1,80p' dagger.json
rg -n 'Host\(|CurrentModule|ModuleSource|Directory\(|Secret|GITHUB_TOKEN|git/config|persist-credentials' .dagger .github

Repository: kyanitecomputer/cairn

Length of output: 2031


🌐 Web query:

Dagger GitHub Action pull request module source .git directory checkout persisted credentials accessibility

💡 Result:

Inspection citation: inspection_997438e8922fe1ee007d7cf214560cca

<source_evidence>

<title>actions/checkout</title> https://github.com/actions/checkout - Safer fork pull request handling: checkout now refuses to check out fork pull request code by default when the workflow is triggered by `pull_request_target` or `workflow_run`. These triggers run with the base repository&`#39`;s `GITHUB_TOKEN`, secrets, and runner access, where executing a fork&`#39`;s code commonly leads to "pwn request" vulnerabilities. - To opt in after reviewing the risks, set the new `allow-unsafe-pr-checkout: true` input. ... - Improved credential security: `persist-credentials` now stores credentials in a separate file under `$RUNNER_TEMP` instead of directly in `.git/config` - No workflow changes required — `git fetch`, `git push`, etc. continue to work automatically - Running authenticated git commands from a Docker container action requires Actions Runner v2.329.0 or later ... This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it. ... The auth token is persisted in the local git config. This enables your scripts to run authenticated git commands. The token is removed during post-job cleanup. Set `persist-credentials: false` to opt-out. ... # Usage ```yaml - uses: actions/checkout@v7 with: # Repository name with owner. For example, actions/checkout # Default: ${{ github.repository }} repository: &`#39`;&`#39`; ... # Personal access token (PAT) used to fetch the repository. The PAT is configured # with the local git config, which enables your scripts to run authenticated git # commands. The post-job step removes the PAT. # # We recommend using a service account with the least permissions necessary. Also # when generating a new PAT, select the least scopes necessary. # # Learn more about creating and using encrypted secrets # # Default: ${{ github.token }} token: &`#39`;&`#39`; ... # SSH key used to fetch the repository. The SSH key is configured with the local ... git config, ... enables your scripts to run authenticated git commands. The ... # post-job step removes the ... account with the ... # Whether to configure the token or SSH key with the local git config # Default: true persist-credentials: &`#39`;&`#39`; ... # Relative path under $GITHUB_WORKSPACE to place the repository path: &`#39`;&`#39`; ... # Whether to checkout submodules: `true` to checkout submodules or `recursive` to # recursively checkout submodules. # # When the `ssh-key` input is not provided, SSH URLs beginning with # `git@github.com:` are converted to HTTPS. # # Default: false submodules: &`#39`;&`#39`; ... # Add repository path as safe.directory for Git global config by running `git # config --global --add safe.directory <path>` # Default: true set-safe-directory: &`#39`;&`#39`; ... # Required to check out fork pull request code from a workflow triggered by # `pull_request_target` or `workflow_run`. These workflows run with the base # repository&`#39`;s GITHUB_TOKEN, secrets, default-branch cache scope, and runner # access; fetching and executing a fork&`#39`;s code in that trusted context commonly # leads to "pwn request" vulnerabilities. Set to `true` only after reviewing the # risks at https://gh.io/securely-using-pull_request_target. # Default: false allow-unsafe-pr-checkout: &`#39`;&`#39`; ... - Scenarios - Fetch only the root files - Fetch only the root files and `.github` and `src` folder - Fetch only a single file - Fetch all history for all tags and branches - Checkout a different branch - Checkout HEAD^ - Checkout multiple repos (side by side) - Checkout multiple repos (nested) - Checkout multiple repos (private) - Checkout pull request HEAD commit instead of merge commit - Checkout pull request on closed event - Push a commit using the built-in token - Push a commit to a PR using the built-in token ... ## Fetch only the root files and `.github` and `src` folder ... ```yaml - uses: actions/checkout@v7 with: sparse-checkout: | .github src ... ## Checkout pull request HEAD commit instead of merge commit ... ```yaml - uses: actions/checkout@v7 with: ref: ${{ github.event.pul…[truncated] <title>TIL: actions/checkout can leak GitHub credentials</title> https://yossarian.net/til/post/actions-checkout-can-leak-github-credentials/ TIL: actions/checkout can leak GitHub credentials # actions/checkout can leak GitHub credentials 2024-08-17 `actions/checkout` is one of GitHub Actions&`#39`; official, blessed actions: it&`#39`;s used by millions of CI workflows to perform `git` repository checkouts. `actions/checkout` also contains a significant security footgun: by default, it&`#39`;ll persist a copy of the workflow&`#39`;s credential (either the default `secrets.GITHUB_TOKEN` one or a custom-supplied one) in the checked-out repository&`#39`;s `.git/config`. This wouldn&`#39`;t be too bad, except that it&`#39`;s somewhat common for CI workflows to then archive and upload the repository as a workflow artifact for use in subsequent steps. A team at Palo Alto Networks found dozens of projects that inadvertently leak their credentials through this and similar artifact vectors. GitHub knows about this and considers it intentional behavior, which seems not great™ to me. If you don&`#39`;t want credentials added to your `.git/config`, they suggest running steps like `actions/checkout` like so: ```yaml uses: actions/checkout@v4 with: persist-credentials: false ``` TL;DR: By default, `actions/checkout` will silently introduce secrets into your repository, even if you maintain proper `git` hygiene and never check any secrets in. The only way to avoid this is to pass `persist-credentials: false` whenever using `actions/checkout`. <title>ci(security): scan secrets via gitleaks CLI in Dagger, not gitleaks-action</title> GitHub pull request 632 in enix/x509-certificate-exporter (link omitted to avoid creating a cross-reference) * **Chores** * Updated secret scanning to run through the existing Dagger pipeline for working-tree scanning. * Hardened CI checkouts by disabling persisted Git credentials and adjusted scan checkout depth. * Updated pinned scanning image versions and refreshed `.gitleaksignore` rules. ... > > > > > > --- > > > > 📝 Walkthrough > > ## Walkthrough > > Adds a `Gitleaks` method to the Dagger module (`dagger/security.go`) that mounts a directory into a pinned gitleaks container and runs a secret scan with exit-code enforcement. Pins the container image in `dagger/base.go`, wires a `security:gitleaks` task into `Taskfile.yml`, migrates the GitHub Actions workflow job from `gitleaks/gitleaks-action` to `dagger/dagger-for-github`, adds gitleaks:allow suppressions to code containing passphrases, and updates `CLAUDE.md` accordingly. > > ## Changes ... > | **Gitleaks image pin and Dagger method** `dagger/base.go`, `dagger/security.go` | Adds `gitleaksImage` digest constant and `X509Ce.Gitleaks(ctx, source)` method that mounts the source directory at `/scan` and runs `gitleaks dir` with `--no-banner`, `--redact`, `--verbose`, and `--exit-code 1`. | ... > | **Taskfile task and integration** `Taskfile.yml` | Adds `security:gitleaks` task invoking `dagger call gitleaks` and includes it in the top-level `security` task before other security checks. | ... > | **CI workflow migration** `.github/workflows/security.yaml` | Replaces the `gitleaks/gitleaks-action`-based secret-leaks job with a `dagger/dagger-for-github` call, removes PR-comment permissions and license/token environment variables, and sets `persist-credentials: false` across multiple security jobs to prevent GitHub token persistence in the checked-out workspace. | ... go` module documentation ... and ignore-scope behavior. ... `56-56`: **Consider disabling persisted Git credentials in the secrets job checkout.** At line 56, `actions/checkout` retains credentials in git config. Since this job executes Dagger code on pull requests, setting `persist-credentials: false` reduces unnecessary token exposure in the workspace. Suggested patch ```diff secrets: name: Secret leaks (Gitleaks) runs-on: ubuntu-latest steps: - uses: actions/checkout@df4cb1c # v6 + with: + persist-credentials: false - uses: dagger/dagger-for-github@27b130b # v8 ``` ... 🤖 Prompt for AI Agents ... In @.github/workflows/security.yaml at line 56, The actions/checkout step at the specified location is retaining Git credentials in the workspace by default. Add the `persist-credentials: false` parameter to the checkout action to disable credential persistence, which reduces unnecessary token exposure when executing Dagger code on pull requests. This parameter should be added as a new line within the checkout step configuration. ... all review comments ... - Line 56: The ... checkout step at ... is retaining Git ... credentials in the workspace by default. Add the `persist-credentials: false` ... parameter to the checkout action to disable credential persistence, which reduces unnecessary token exposure when executing Dagger code on pull requests. ... This parameter should be added as a new line within the checkout step configuration. ... **Add `persist-credentials: false` for security hardening.** The checkout step should explicitly disable credential persistence to prevent the GitHub token from being exposed to subsequent steps. This is especially important in security workflows that invoke third-party tools or containers. 🔒 Proposed fix ```diff - uses: actions/checkout@df4cb1c # v6 + with: + persist-credentials: false ``` 🤖 Prompt for AI Agents ``` ... In @.github/workflows/security.yaml at line 56, The actions/checkout step in the security workflow at line 56 lacks the persist-credentials: false configuration option. Add persist-credentials: false as …[truncated] <title>Remove `persist-credentials` or change the default to `false` · Issue `#485` · actions/checkout · GitHub</title> GitHub issue 485 in actions/checkout (link omitted to avoid creating a cross-reference) Remove `persist-credentials` or change the default to `false` · Issue `#485` · actions/checkout · GitHub # Remove persist-credentials or change the default to false `#485` New issue New issue Open opened on Apr 26, 2021 Currently one has to resort to explicitly specifying`persist-credentials: false` to avoid the credentials being persistent. My understanding is that persisting the credentials gives every step in the job that occurs after`actions/checkout@v2` implicit access to the token. This is not what people expect and this leads people to write jobs that expose their repo to more risk than they otherwise would. I propose the`persist-credentials` feature be removed completely and then v3 be released. Otherwise, if that&`#39`;s not practical, then at least the default should be changed to`false`. <title>fix: use git credential helper&`#39`;s username for modules with http auth · Pull Request `#10427` · dagger/dagger</title> GitHub pull request 10427 in dagger/dagger (link omitted to avoid creating a cross-reference) ## fix: use git credential helper&`#39`;s username for modules with http auth ... In `#8989`, an issue emerged with GitLab CI&`#39`;s `CI_JOB_TOKEN`s: Right now, when accessing remote modules via HTTP authentication using `dagger call -m gitlab.com/...`, the username is hardcoded to `x-access-token` (except for bitbucket.com). However, as specified in [GitLab’s documentation](https://docs.gitlab.com/ci/jobs/ci_job_token/#use-a-job-token), username `gitlab-ci-token` is required when using basic authentication with `CI_JOB_TOKEN`. In the discussion, we recognized that not only receiving the secret but also the username from the git credential helper would solve the issue best. ... - Add another option `httpAuthUsername` to `RemoteGitRepository` and pass the username alongside `httpAuthToken`. It would also change the interface of [`GitRepository`](https://docs.dagger.io/api/reference/#definition-GitRepository), and while this isn&`#39`;t related to the original problem, it makes it easier to set the username when calling a git repo from within a module (currently, the [`httpAuthHeader`](https://docs.dagger.io/api/reference/#query-git) arg must be composed manually). This was my first approach until I realized I could solve the issue without extensive code refactoring. However, I was considering adding two helper functions to [`GitRepository`](https://docs.dagger.io/api/reference/#definition-GitRepository): `.WithHttpAuthHeaderFromBasicAuth(username, password)` and `.WithHttpAuthHeaderFromBearerAuth(token)` to simplify [`httpAuthHeader`](https://docs.dagger.io/api/reference/#query-git) formatting. ... - Use `httpAuthHeader` instead of `httpAuthToken` to leverage existing code with minimal refactoring. While this solution works for the PR, it reveals overlapping logic between `core.RemoteGitRepository.setup` and `core.schema.gitSchema.git`. Further refactoring may be beneficial to address this duplication. ... Currently, this PR doesn&`#39`;t address another desirable CLI feature: being able to pass credentials within the URL. At present, username and password parameters in URLs are ignored to best of my knowledge. Adding this capability would allow using the format `dagger call -m https://gitlab-ci-token:${CI_JOB_TOKEN}`@gitlab.com/`...` and also passing repositories as directories from the command line in general. This would eliminate the need to set up a git credential helper, which can be cumbersome in some CI environments. However, during implementation, I discovered that additional refactoring is needed to make this work. Therefore, this PR only addresses the GitLab CI issue from `#8989`. ... > LGTM, we need a test though. > > `@grouville` why do we need to make changes to the test repo here? This should just be possible to test just using `ExperimentalServiceHost`. > > There&`#39`;s no tests that cover the behavior of the auto-loading credentials for just plain git clones (only for modules). https://github.com/dagger/dagger/blob/84dc5b3ba37238d957db1025c70dca797d35a055/core/integration/module_test.go#L5774-L5800 > > We should adapt the above to `git_test.go` and add tests for this through there. ... > Hey `@jedevc`, I’m not 100% sure I’ve fully grasped all your points here 🙏👼 > > Just to confirm my understanding clearly: > > 1. On the testing approach, you asked: > > > `@grouville` why do we need to make changes to the test repo here? This should just be possible to test just using ExperimentalServiceHost. > > My initial intent was testing the specific GitLab edge case (CI_JOB_TOKEN requires explicit username). But your suggestion seems broader: instead of an integration test using a real token, you’d prefer spinning up a local git server with ExperimentalServiceHost and checking credentials locally. Did I get that right? > > 1. About “plain git clones”, you wrote: > > > `There&`#39`;s no tests that cover the behavior of the auto-loading credentials for just plain git …[truncated]

Citations:

  • 1: https://github.com/actions/checkout
  • 2: https://yossarian.net/til/post/actions-checkout-can-leak-github-credentials/
  • 3: GitHub pull request 632 in enix/x509-certificate-exporter (link omitted to avoid creating a cross-reference)
  • 4: GitHub issue 485 in actions/checkout (link omitted to avoid creating a cross-reference)
  • 5: GitHub pull request 10427 in dagger/dagger (link omitted to avoid creating a cross-reference)
  • 6: GitHub pull request 6992 in dagger/dagger (link omitted to avoid creating a cross-reference)
  • 7: GitHub pull request 9131 in dagger/dagger (link omitted to avoid creating a cross-reference)
  • 8: GitHub issue 8989 in dagger/dagger (link omitted to avoid creating a cross-reference)
  • 9: GitHub pull request 6575 in dagger/dagger (link omitted to avoid creating a cross-reference)

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- .dagger outline ---'
ast-grep outline .dagger/main.go --view expanded
printf '%s\n' '--- host/filesystem/process-related symbols ---'
rg -n -C 3 'Host\(|Client\(|Directory\(|WithDirectory|WithMounted|WithExec|Exec\(|Open|Read|File\(|NewFile|dagger\.|os\.(ReadFile|Open|Getwd|Stat|ReadDir)|filepath|exec\.Command|GITHUB_TOKEN|git/config|\.git' .dagger/main.go
printf '%s\n' '--- module source and CI references ---'
cat -n dagger.json
cat -n .github/workflows/ci.yml

Repository: kyanitecomputer/cairn

Length of output: 12801


🌐 Web query:

Dagger Go SDK module sandbox Host Directory access from a module and dagger call GitHub Actions workspace

💡 Result:

Inspection citation: inspection_b5cfbf450be3939450522f7ad3fe2fdf

<source_evidence>

<title>Add `Workspace` API and caching support (`#11874`) · b5ea047 · dagger/dagger</title> https://github.com/dagger/dagger/commit/b5ea047d5e543c7d8524e28cfd2342944e147bcc - api: add Workspace.directory and Workspace.file ... Add directory() and file() methods to the Workspace type, providing host-backed filesystem access scoped to the workspace root. ... - directory(path, include, exclude) returns a Directory snapshot from the host, with the path resolved relative to the workspace root. Delegates to Host.Directory for the actual host filesystem sync. ... - file(path) returns a single File from the host, resolved relative to the workspace root. Uses the same host directory + file extraction pattern as Host.file. ... - fix: route Workspace.directory/file through the workspace owner&`#39`;s client session ... When a module function receives an injected Workspace and calls workspace.directory() or workspace.file(), the host filesystem operation needs to go through the original client&`#39`;s session — not the module&`#39`;s session (which doesn&`#39`;t have host access). ... The fix: ... - Store the creating client&`#39`;s ID in the Workspace object when it&`#39`;s constructed by currentWorkspace - In the directory/file resolvers, override the client metadata in context to the workspace&`#39`;s owning client ID before routing through the host.directory dagql pipeline - This ensures the filesync request reaches the correct session that has access to the host filesystem ... - Basic workspace arg: module reads a file from host via Workspace - Directory entries: module lists workspace directory contents - Include/exclude patterns on Workspace.directory - Cache invalidation: host filesystem changes reflected without cache buster - Workspace.file: reading file content from host - Subdirectory access via Workspace.directory - Workspace args are magical (not exposed as CLI flags, but function is callable) ... All tests use the Dang SDK and the &`#39`;dagger module init&`#39`; command to scaffold workspace modules. ... - add + use pathutil.SandboxedRelativePath ... The workspace schema had a local resolveWorkspacePath function that resolved user-provided paths relative to a root directory while preventing directory traversal escapes. This had two problems: ... 1. It used filepath (OS-dependent) instead of path (POSIX), which ... would break on Windows clients. The engine always runs on Linux, but workspace roots from Windows clients arrive as forward-slash paths with drive prefixes (e.g. &`#39`;C:/Users/foo&`#39`;) since the client normalizes backslashes before sending. Linux&`#39`;s filepath.IsAbs doesn&`#39`;t recognize &`#39`;C:/...&`#39`; as absolute, and filepath.Clean won&`#39`;t handle drive letters correctly. ... Moved the logic to pathutil.SandboxedRelativePath which: ... - Normalizes backslashes to forward slashes for Windows client paths - Strips drive letters (via pathutil.GetDrive) or leading &`#39`;/&`#39`; so absolute user paths are treated as relative to the root ... - Uses normalizePath (already in pathutil) for consistent handling - Verifies the resolved path doesn&`#39`;t escape root via &`#39`;..&`#39`; ... Also switched workspace.go from filepath to path (POSIX) throughout, since all path operations run on the Linux engine against forward-slash-normalized paths. ... Added 12 unit tests ... relative paths, dot, absolute paths, ... dotdot escape attempts, Windows backslashes, drive letters, and Windows roots with drive prefixes. ... +// TestWorkspaceBlueprint verifies that a blueprint module accepting a Workspace +// argument can access the host filesystem, just like a toolchain module. +func (WorkspaceSuite) TestBlueprint(ctx context.Context, t *testctx.T) { + c := connect(ctx, t) + + ctr := workspaceBase(t, c). + WithNewFile("hello.txt", "hello from workspace"). ... + With(initDangBlueprint("greeter", ` ... +// TestWorkspaceArg verifies that a module function accepting a Workspace +// argument can access the host filesystem. +func (WorkspaceSuite) TestWorkspaceArg(ctx context.Context, t *testctx.T) { + c := connect(ctx, t) + + ctr := workspaceBase(t, c). + WithNewFile("hello.txt…[truncated] <title>Remove Host API from module SDK codegen. · Pull Request `#6535` · dagger/dagger</title> GitHub pull request 6535 in dagger/dagger (link omitted to avoid creating a cross-reference) ## Remove Host API from module SDK codegen. ... ## Modules that use the Host API to read source files ... Previously, module containers set their working directory to the directory containing the module&`#39`;s source code. You could then read files from that directory with calls like `dag.Host().File("./some/file.txt")`. ... Now, module containers are set to an initially empty scratch directory, so those calls will not work anymore. ... To use files/directories from the module source code, you can instead: ... 1. Directories - `dag.CurrentModule().Source().Directory("some/subdir")` 2. Files - `dag.CurrentModule().Source().File("some/file.txt")` ... Additionally, it used to technically be possible to use files/directories from anywhere in the root dir loaded for a module in the case where `"root"` was configured in `dagger.json`. This is no longer possible; instead anything you need to use from outside the module&`#39`;s source code directory should be accepted as an argument to a function or the module constructor. ... One final use case to mention: if you need to create new files/directories and then load them into the dagger API during module execution, this is still possible. ... 1. Directories - `dag.CurrentModule().Workdir("./some/dir")` 2. Files - `dag.CurrentModule().WorkdirFile("./some/file.txt")` ... The new dag.CurrentModule() API replaces the need for the Host API and gives more controlled functionality that is specific to modules. ... > I only ran a subset of tests locally, but didn&`#39`;t run `TestModuleDaggerCallArgTypes/service_args` which I now see is failing due to the fact that you pass a service created w/ the host API from the CLI to a module, which no longer knows anything about host and thus can&`#39`;t load its ID... > > Been thinking through whether there&`#39`;s any reasonable options here that also maintains the current approach, but don&`#39`;t see any; only see options that would hack dagql in unreasonable ways. > > I am going to give a shot at a different approach that still allows the host schema to be loaded for module clients on the graphql level, but just not presented to SDKs for codegen. Should create same end effect while still allowing use cases like this to work. ... The new approach described in previous comment works much ... and thankfully isn ... t quite as kludgy as I originally ... : https://github.com/dagger/dagger/pull/6535/commits/685e6e52862ad2bb0a296a0546a02656e3538618 > > I could actually see it being useful in general outside this use case; e.g. I considered doing the same thing to block the `Query.blob` API from *all* SDK clients since that&`#39`;s very much internal only (backed out because I really don&`#39`;t want to expand ... scope of this too much last minute ... > If we&`#39`;re removing the host api entirely, it&`#39`;s now not possible to tunnel/reverse-tunnel a service from the module container? > > I quite liked this feature, and I&`#39`;m not sure if we should remove it - though maybe it does genuinely belong somewhere else (e.g. under `Local` as in https://github.com/dagger/dagger/pull/6408). ... > **Review (approved):** > Happy to take this now, since it&`#39`;ll be good to remove the access to the host files for this release. > > But also, I want to make sure that https://github.com/dagger/dagger/pull/6535#issuecomment-1921051137 doesn&`#39`;t get lost, and that we have a way to restore this in the future. > > (ahhh, I&`#39`;ve realized we can do tunnels using the chainable `up` to do this) <title>Details on status of dagger.Host in SDK docs · Issue `#7998` · dagger/dagger</title> GitHub issue 7998 in dagger/dagger (link omitted to avoid creating a cross-reference) # Issue: dagger/dagger `#7998` - Repository: dagger/dagger | Automation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud | 16K stars | Go ## Details on status of dagger.Host in SDK docs - Author: [`@ghost`](https://github.com/ghost) - State: closed (completed) - Labels: area/sdk, area/sdk/python - Created: 2024-07-22T11:00:41Z - Updated: 2024-07-23T13:07:39Z - Closed: 2024-07-23T13:07:39Z ### What is the issue? When working on Python pipelines with Dagger, I frequently reference the [SDK&`#39`;s documentation](https://dagger-io.readthedocs.io/en/sdk-python-v0.12.1/), and it&`#39`;s a very helpful resource. During my work, I discovered the [dagger.Host object](https://dagger-io.readthedocs.io/en/sdk-python-v0.12.1/client.html#dagger.Host) and the [dagger.Client.host method](https://dagger-io.readthedocs.io/en/sdk-python-v0.12.1/client.html#dagger.Client.host), which suggested that these could be used to retrieve host information. However, I encountered an issue in practice where the following code snippet. ```python `@function` def get_host(self) -> dagger.Host: return dag.host() ``` throws the error: ``` AttributeError: module &`#39`;dagger&`#39`; has no attribute &`#39`;Host&`#39`; ``` I couldn&`#39`;t type hint the function since it doesn&`#39`;t recognize the existence of a `dagger.Host` type. After modifying the function to return a `dagger.Directory` to avoid type hint issues: ```python `@function` def get_host_dir(self) -> dagger.Directory: return dag.host().directory(&`#39`;.&`#39`;) ``` I encountered a similar error: ``` Function execution error: &`#39`;Client&`#39`; object has no attribute &`#39`;host&`#39`; ``` This led me to believe that the `host`-related stuff may have been removed from the SDK. Further research led me to this PR (`#6535`) which seems to be the answer to my question. Using the following code: ```python `@function` def get_host(self) -> dagger.Directory: return dag.current_module().source() ``` would now return that module&`#39`;s directory. If my understanding is correct, `Host` is a thing of the past and these are just leftovers in the documentation that weren&`#39`;t removed? I took a look at Go SDK docs and [Client.Host()](https://pkg.go.dev/dagger.io/dagger#Client.Host) is also listed there without any details on its removal on end-user side. I got an impression it&`#39`;s a thing I can use. Should the `Host`-related stuff be removed from the documentation, or at least be updated with a more detailed description and a redirect to `CurrentModule`? I could use and would appreciate more details on this in the documentation. --- ### Timeline **`@helderco`** commented · Jul 22, 2024 at 11:38am > That&`#39`;s just the **reference documentation**, it doesn&`#39`;t have the whole picture. It&`#39`;s expected that you rely more on the [Dagger docs](https://docs.dagger.io) for learning. In this case in particular, see [Execution Environment](https://docs.dagger.io/manuals/developer/execution-environment). > > To answer your question, `Client.host` is not deprecated, just not available in Dagger Modules, but still usable when not using Modules. **`@ghost`** commented · Jul 22, 2024 at 12:02pm · Author > I see, what would be other ways of using Dagger outside of Modules? I can think of sending GraphQL queries to the Dagger Engine, but is there something else other than that? So far, I was rather focused on development with the SDK and modules seemed to be the thing. **`@helderco`** commented · Jul 22, 2024 at 1:29pm > Some people haven&`#39`;t migrated to Modules yet. Running Dagger pipelines directly from the host is now considered an advanced/niche use case, but still allowed. Right now, the big downside to that is that it&`#39`;s not easy to interact with Modules yet. You can do it with raw GraphQL queries but you won&`#39`;t get generated client bindings for the Modules that you want to use (e.g., `dag.trivy()`). We plan on having this fixed though. > > Main advantage for this use …[truncated]

Citations:


🌐 Web query:

Dagger 0.21 module +defaultPath "." Directory input excludes .git files source filtering documentation

💡 Result:

Inspection citation: inspection_06a4c4479dfb57b9f3ae0f35216681f1

<source_evidence>

<title>Dagger 0.13: First-class Monorepo Support, Private Modules, a New CLI Command, and More | Dagger Blog</title> https://dagger.io/blog/dagger-0-13/ #### Pre-call filtering ... Dagger Functions filter the contents of directories they receive as arguments. Crucially, these filters are applied before the function is called. This leads to massive performance gains in monorepos, for two reasons: ... 1. Optimized uploads. ... the Dagger ... your function needs. The more files in your repository, the more drastic the ... 2. Better caching. Pre- ... filtering means pre-cache filtering: local files ... by your function ... will not invalidate its cache if they ... Both features - context directory and pre-call filtering - are used by annotating arguments to your function. Here is an example: ... Golang ``` func (m *Example) Example( // +defaultPath="/" // +ignore=[“.git”, “*.env”, “.github”] repo *dagger.Directory, // +defaultPath="." moduleDir *dagger.Directory, // +defaultPath=”/README.md” readme *dagger.File ) (...) {...} ``` ... ``` `@func`() async example( `@argument`({ defaultPath: "/", ignore: [“.git”, “*.env”, “.github”] }) repo: Directory, `@argument`({ defaultPath: "." }) moduleDir: Directory, `@argument`({ defaultPath: “/README.md”}) readme: File, ): any {...} ``` ... ``` `@function` async def example( self, repo: Annotated[dagger.Directory, DefaultPath("/"), Ignore([“.git”, “*.env”, “.github”])], moduleDir: Annotated[dagger.Directory, DefaultPath(".")], readmeFile: Annotated[dagger.File, DefaultPath("/README.md")] ) : ``` <title>Feat/context directory · Pull Request `#7744` · dagger/dagger</title> GitHub pull request 7744 in dagger/dagger (link omitted to avoid creating a cross-reference) This PR updates the `Function` and `FunctionArg` from our engine API to support `defaultPathFromContext` ... ```gql # Function withArg( """ If the argument is a Directory or File type, default to load path from context directory, relative to root directory. """ defaultPath String = "" ignore: [String!] # } ... # FunctionArg { """ If the argument is a Directory or File type, default to load path from context directory, relative to root directory. """ defaultPath: String! ignore: [String!] # } ``` ... - Update `Call` to load contextual values and insert it as input. - Add a `loadContextualArg` function that take the typedef and retrieve its contextual default value. - Add a function `loadContext` to load from `git` or `local` source the contextual path. ... This implements the spec designed in `#7647` For git: we load it from the context directory since it includes the whole repo. For local: we load it from `importLocal` with the specific directory specified so we never load the full repo expect for `/` ... - [x] The context directory is the git repository containing the module. If there is not git repository, the context is simply the module. - [x] Relative paths are relative to the current source file, scoped to the context directory for security - [x] Absolute paths are rooted in the context directory - [x] There is no limit to the number of directories which can have default values of this sort - [x] Optionally, directories can be filtered with a gitignore-style annotation ... - [x] Go using pragmas directive on `Directory` or `File` type ⌛ - [x] Typescript using `@argument({ defaultPath: "path", ignore: ["xx", "!xx"] })` ✅ ... ```typescript `@func`() build( `@argument`({ defaultPath:"./frontend/src" }) frontend: Directory, `@argument`({ defaultPath:"./backend/app/src" }) backend: Directory, `@argument`({ defaultPath: "./tools/builder.conf" }) buildConfig: File, ): Directory {} ``` ... 7-L ... > > I thought we were using default values? I might have missed (or forgotten) the last round of bikeshedding on that topic. > > I updated my view in https://github.com/dagger/dagger/issues/7647#issuecomment-2174570033: > > > [...] warming up to not using the default value because of that as it makes an overall simpler implementation, just sacrificing on DX for SDKs that support default values, but that can be improved later. > > --- > > > * [x] Typescript using `@context("path")` ⚡ to be tested once the engine changes are completed > > * [ ] Python using `@context("path")` ? To be confirmed by `@helderco` ⌛ > > We need to bikeshed on the `@context` name. > > In Python, this would have to be done using metadata annotation on the type: > > ```python > def build( > self, > backend: Annotated[ > dagger.Directory, > DefaultFromContext("../backend/app/src"), > Ignore(...), > ] > ) -> dagger.Directory: > ... > ``` ... > > `@TomChv` could you add example snippets of what that feature would look like, in the 3 main languages please? It would make it easier to discuss the DX tradeoffs > > I think it should look like this: > > **Go** > > ```go > func (app *App) Build( > // +default="./frontend/src" > frontend *Directory, > // +default="./backend/app/src" > backend *Directory, > // +default="/tools/builder.conf" > buildConfig *File, > ) *Directory {} > ``` > > **Typescript** > > ```typescript > `@func`() > build( > `@context`("./frontend/src") frontend: Directory, > `@context`("./backend/app/src") backend: Directory, > `@context`("./tools/builder.conf") buildConfig: File, > ): Directory {} > ``` > > **Python** > > Done by Helder above > > > ```py…[truncated] <title>feat/context directory (`#7744`) · 9c2c03f · dagger/dagger</title> https://github.com/dagger/dagger/commit/9c2c03f19d860a1c50f39340613ee4910b5d877e Add `defaultPath` metadata to an argument to make it contextual. This is also usable on `Directory` & `File` for now. ... This will let the Engine load the artifact before actually callling the function which simplify the caching and make the parameter optional. ... This is extra useful for redondant parameter such as source code or ... config files. ... Path can be either relative or absolute: ... - If absolute: load from the context dir (.git) - If relative: load from the root dir (dagger.json) ... Add `ignore` metadata to an argument to include or exclude specified files. The ignore patterns follow `.gitignore` convention so prefixing a path with `!` will actually include the file. ... spec *func ... Code = append(argOptsCode, Id("DefaultValue").Op(":").Id("dagger").Dot("JSON").Call(Lit(argSpec.defaultValue ... } + if argSpec.defaultPath != "" { + argOptsCode = append(argOptsCode, Id("DefaultPath").Op(":").Lit(argSpec.defaultPath)) + } ... + ignores ... + defaultPath := "" + if v, ok := pragmas["defaultPath"]; ok { + defaultPath = v + if strings.HasPrefix(v, `"`) && strings.HasSuffix(v, `"`) { + defaultPath = v[1 : len(v)-1] + } + + optional = true // If defaultPath is set, the argument becomes optional + } ... []string{} ... , ok := pragmas["ignore"]; ... ("ignore pragma &`#39`;% ... ps *parseState) ... (field *types. ... docComment string, lin ... @@ -326,4 +358,13 @@ type paramSpec struct { // parent is set if this paramSpec is nested inside a parent inline struct, // and is used to create a declaration of the entire inline struct parent *paramSpec + + // Only applies to arguments of type File or Directory. + // If the argument is not set, load it from the given path in the context directory + defaultPath string + + // Only applies to arguments of type Directory. + // The ignore patterns are applied to the input directory, and + // matching entries are filtered out, in a cache-efficient manner. + ignore []string } ... +func (t *Test) Dirs( + ctx context.Context, + + // +defaultPath="/" + root *dagger.Directory, + + // +defaultPath="." + relativeRoot *dagger.Directory, +) ([]string, error) { + res, err := root.Entries(ctx) + if err != nil { + return nil, err + } + relativeRes, err := relativeRoot.Entries(ctx) + ... { + return ... , err ... } + ... res, relativeRes...), ... +func (t *Test) DirsIgnore( + ctx context.Context, + + // +defaultPath="/" + // +ignore=["!backend", "!frontend"] + root *dagger.Directory, + + // +defaultPath="." + // +ignore=["dagger.json", "LICENSE"] + relativeRoot *dagger.Directory, +) ([]string, error) { + res, err := root.Entries(ctx) + if err != nil { + return nil, err + } + relativeRes, err := relativeRoot.Entries(ctx) + if err != nil { + return nil, err + } + return append(res, relativeRes...), nil +} ... +func (t *Test) RootDirPath( + ctx context.Context, + + // +defaultPath="/backend" + backend *dagger.Directory, + + // +defaultPath="/frontend" + frontend *dagger.Directory, + + // +defaultPath="/ci/dagger/sub" + modSrcDir *dagger.Directory, +) ([]string, error) { + backendFiles, err := backend.Entries(ctx) + if err != nil { + return nil, err + } + frontendFiles, err := frontend.Entries(ctx) + if err != nil { + return nil, err + } + modSrcDirFiles, err := modSrcDir.Entries(ctx) + if err != nil { + return nil, err + } + + res := append(backendFiles, append(frontendFiles, modSrcDirFiles...)...) + + return res, nil +} ... ctx context.Context ... // +defaultPath="./dagger/sub" + modSrcDir *dagger.Directory, + + // +defaultPath="../backend" + backend *dagger.Directory, +) ... , error) { + modSrcDir ... , err := modSrcDir.Entries(ctx) + if err != nil { + return ... , err + } + backendFiles, err := backend.Entries(ctx) + if err != nil { …[truncated] <title>core/integration/module_path_inputs_test.go</title> https://github.com/dagger/dagger/blob/c7d72b6c/core/integration/module_path_inputs_test.go // These tests cover module arguments that receive filesystem or Git content. // They verify how `+defaultPath`, `+ignore`, `.gitignore`, and explicit filters // shape Directory, File, and GitRepository inputs before module code sees them. // ... func (t *Test) Dirs( ctx context.Context, // +defaultPath="/" root *dagger.Directory, // +defaultPath="." relativeRoot *dagger.Directory, ) ([]string, error) { res, err := root.Entries(ctx) if err != nil { return nil, err } relativeRes, err := relativeRoot.Entries(ctx) if err != nil { return nil, err } return append(res, relativeRes...), nil } ... func (t *Test) DirsIgnore( ctx context.Context, // +defaultPath="/" // +ignore=["**", "!backend", "!frontend"] root *dagger.Directory, // +defaultPath="." // +ignore=["dagger.json", "LICENSE"] relativeRoot *dagger.Directory, ) ([]string, error) { res, err := root.Entries(ctx) if err != nil { return nil, err } relativeRes, err := relativeRoot.Entries(ctx) if err != nil { return nil, err } return append(res, relativeRes...), nil } ... func (t *Test) RootDirPath( ctx context.Context, // +defaultPath="/backend" backend *dagger.Directory, // +defaultPath="/frontend" frontend *dagger.Directory, // +defaultPath="/ci/dagger/sub" modSrcDir *dagger.Directory, ) ([]string, error) { backendFiles, err := backend.Entries(ctx) if err != nil { return nil, err } frontendFiles, err := frontend.Entries(ctx) if err != nil { return nil, err } modSrcDirFiles, err := modSrcDir.Entries(ctx) if err != nil { return nil, err } res := append(backendFiles, append(frontendFiles, modSrcDirFiles...)...) return res, nil } ... func (t *Test) RelativeDirPath( ctx context.Context, // +defaultPath="./dagger/sub" modSrcDir *dagger.Directory, // +defaultPath="../backend" backend *dagger.Directory, ) ([]string, error) { modSrcDirFiles, err := modSrcDir.Entries(ctx) if err != nil { return nil, err } backendFiles, err := backend.Entries(ctx) if err != nil { return nil, err } return append(modSrcDirFiles, backendFiles...), nil } ... ctx context.Context, ... defaultPath="/ci/ ... " license * ... .File, // +defaultPath="./dagger/sub ... " index *dagger.File, ) ... string, error) { licenseName ... err := license ... Name(ctx) if err != nil { return ... , err } indexName ... err := index ... Name(ctx) if err != nil { return nil, err } return []string{licenseName, indexName}, nil } ... out, ... frontend/\ndagger/\ ... " switch tc ... typescript": ... func (ModuleSuite) TestDefaultPathAndIgnoreUseRemoteModuleSource(ctx context.Context, t *testctx.T) { c := connect(ctx, t) remoteModule := c.Directory(). WithNewFile("source.txt", "module source"). WithNewFile("filtered/keep.txt", "module keep"). WithNewFile("filtered/drop.txt", "module drop"). WithNewFile("dagger.json", `{"name":"reader","sdk":{"source":"go"}}`). WithNewFile("main.go", `package main ... // ReadDefaultFile proves a File +defaultPath is resolved from this module source. func (m *Reader) ReadDefaultFile( ctx context.Context, // +defaultPath="/source.txt" source *dagger.File, ) (string, error) { return source.Contents(ctx) } ... // ReadDefaultDirectoryFile proves a Directory +defaultPath is resolved from this module source. func (m *Reader) ReadDefaultDirectoryFile( ctx context.Context, // +defaultPath="/filtered" filtered *dagger.Directory, ) (string, error) { return filtered.File("keep.txt").Contents(ctx) } ... // ListIgnoredDefaultDirectory proves +ignore applies to the defaultPath directory from this module source. func (m *Reader) ListIgnoredDefaultDirectory( ctx context.Context, // +defaultPath="/filtered" // +ignore=["drop.txt"] filtered *dagger.Directory, ) ([]strin…[truncated] <title>+ignore="git": optionally apply gitignore to a directory argument</title> GitHub issue 11317 in dagger/dagger (link omitted to avoid creating a cross-reference) # +ignore="git": optionally apply gitignore to a directory argument - State: closed - Author: shykes - Created: 2025-10-28T21:45:42Z - Updated: 2026-03-25T05:18:31Z - Repository: dagger/dagger - Number: `#11317` --- Dagger can natively load .gitignore and apply its rules to filter host directories pre-upload. This feature is disabled by default, and can be enabled as an argument to `Host.directory()`. But, it can NOT be enabled by a function to specify pre-call filtering on its own arguments... In other words, we are missing +gitignore (or +ignore="git") Perhaps this is easier to implement by piggypacking on an existing directive (`+ignore`) which is already supported by all SDKs? But will SDKs allow passing a string instead of an array? ## Timeline - Renamed from "+gitignore: optionally apply gitignore to a directory argument" to "+ignore="git": optionally apply gitignore to a directory argument" **vito** commented on 2025-10-28T22:58:23Z: > > Perhaps this is easier to implement by piggypacking on an existing directive (`+ignore`) which is already supported by all SDKs? But will SDKs allow passing a string instead of an array? > > I see the appeal, but I suspect supporting two types will end up being just a _different kind_ of change to make to each SDK. > > In defense of adding a pragma, it should be a well-beaten path by now, the only inventive thing really is how to "when in rome" it in each SDK. It&`#39`;s laborious for sure, though. > > But if we want, we _could_ sidestep it by adding a special syntax to the ignore rules themselves - then it&`#39`;s always `[]string`: > > ```go > // A directory that ignores `.dagger` and whatever&`#39`;s specified by `.gitignore`. > // > // +ignore=[".dagger", "+.gitignore"] > src *dagger.Directory, > ``` > > We already handle a `!` prefix so handling `+` or some other uncommon path prefix symbol seems OK to me. > > (More to bikeshed, but I&`#39`;ll pause here in case twisting whatever this pseudostandard is is unpalatable.) **shykes** commented on 2025-10-29T18:51:28Z: > `@vito` I think it&`#39`;s safer to have a separate pragma. It&`#39`;s true that we already have `!foo` as a precedent, and `+foo` could make sense to mean "include ignores from this other file", but: > > 1. There would be *some* confusion from people thinking it means "definitely include this file" or some other interpretation > 2. We would need to support multiple ignore file formats under the same `+` directive: `.gitignore`, but in the future `.dockerignore`, and perhaps a dagger-specific format that simply is a json serialization of our own directives? That feels a little too much. > 3. (as mentioned by `@jedevc` in discord). "apply git ignore" is not exactly the same as "apply this gitignore file", as there might be several cascading files etc. so there&`#39`;s an impedance mismatch. > > > TLDR I think `+gitignore` is the safe choice - vito mentioned - vito subscribed - jedevc mentioned - jedevc subscribed **Neirda24** commented on 2025-11-24T20:01:58Z: > Hi. Jumping in here to give a few other usecases. My example are based on PHP needs but I think it can apply to any other languages. > > 1. Yes ignoring based on .gitignore is a must have... BUT we can have multiple .gitignore in a given repository so the directory object should be able ot ignore everything from any .gitignore file. > 2. Given we are working closely to docker features, supporting `.dockerignore` would seem appropriate and relevant. Especially for easy migration. WDYT ? > 3. Any custom file could be defined as an "ignore" list and I think it should opt in to have several ignore files. > 4. In many cases we want to "include" instead of ignore. Because it requires less maintenance. For php-cs-fixer for example we apply it …[truncated]

Citations:


Sensitive Data Exposure

Reachability: External
Exploitability: Trivial
CWE: CWE-522 — Insufficiently Protected Credentials

Disable persisted checkout credentials before running Dagger.

Ci.src uses +defaultPath=".", so Dagger passes the repository context to the module without excluding .git. Pull-request-controlled Dagger code can read src.File(".git/config") and exfiltrate the checkout token. Dagger host access is not required.

contents: read prevents write operations but still exposes a bearer token with repository-content read access.

Suggested fix
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        with:
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.30.0)

[warning] 25-26: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

Comment thread README.md
Comment on lines +89 to +90
CPTRA_IMGTOOL=../cptra_imgtool PREBUILT=/path/to/a2-prebuilts \
tools/a2/gen-a2-image.sh

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document the host TamaGo requirement for A2.

tools/a2/gen-a2-image.sh directly runs ${TAMAGO:-tamago}. A user who follows this command after the stated Dagger-only setup gets tamago: command not found. Document the required host TamaGo compiler and the TAMAGO override.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant