CI/CD and Software Supply Chain Attack Surface
Self-hosted runners, Jenkins, dependency confusion, artifact poisoning, npm and PyPI trust, secrets in git history and Postman collections in a repo.
A build system is a privileged execution environment that accepts input from everyone who can open a pull request. These notes follow that input to the credentials it reaches.
Self-Hosted Runners
A persistent runner inside the network turns pipeline code into code running near credentials, sockets and internal services. Self-hosted runners inherit host filesystem, network, tooling and identity, unlike ephemeral runners, and state may survive between jobs. Untrusted pull requests, caches, workspaces, Docker sockets, cloud metadata and local credentials create paths from repository code into infrastructure. I identify which workflows reach the runner, who can modify those workflows and which resources the host can access. A repository-dedicated runner can still be dangerous when the same trust boundary mixes fork code with deployment credentials. Isolation must follow job trust level.
1
2
3
4
id
mount
ls -l /var/run/docker.sock 2>/dev/null
env | sed -n '1,40p'
Dependency Confusion
A leaked internal package name can become code execution when the resolver prefers a public package with a higher version. Dependency confusion comes from resolution across internal and public registries. Name, version and priority determine which package is installed. npm, pip, NuGet and other package managers use different resolution rules. Registry configuration and namespaces change the technique. I identify internal names leaked through manifests, lockfiles, errors and artifacts. In a lab I publish only controlled namespaces with no third-party collision. Testing must avoid squatting on names you do not own in public registries. The clean proof uses a lab registry or a package name reserved for the client.
1
grep -R 'dependencies\|requirements\|PackageReference' -n . | head -50
Secrets in Git History
Deleting a password from the latest commit removes the line from view. The old object may remain inside the repository for a long time. Git preserves history through objects, commits, trees and blobs. Deleting a branch does not guarantee immediate removal of old content. Reflogs, tags, forks, CI clones, packfiles and caches may retain secrets after the visible fix. I use git log, git rev-list, git show and secret scanners to find where the value entered and which references still point to it. Rewriting history does not invalidate the credential. Rotation comes first. Git cleanup reduces residual exposure after the old secret is already dead.
1
2
3
git log --all --stat
git rev-list --objects --all | head
git fsck --full --unreachable
Jenkins
Jenkins often concentrates code, credentials and remote execution in one place. A seemingly limited user may be one job away from another trust boundary. Jenkins organizes controllers, agents, jobs, pipelines, plugins and credentials. Configure, build and script permissions have very different impact. A controlled Jenkinsfile may execute on an agent holding secrets. Script Console, when reachable, provides direct administrative power on the controller. I enumerate version, authentication, authorization strategy, nodes, credential bindings and who can modify pipelines before triggering builds. Credential masking does not stop job code from using the value. Self-hosted agents should be treated as privileged hosts.
1
curl -s https://jenkins.example.test/api/json | jq .
CI/CD Artifact Poisoning
If a privileged job executes an artifact produced in a less-trusted context, the trust boundary crossed the pipeline inside a ZIP file. Artifacts carry binaries, scripts, manifests and metadata between jobs or workflows. Signatures and provenance are not always verified. A pull-request job without secrets may produce an artifact later downloaded by a deployment workflow holding production credentials. I follow the producer, storage, retention, consumer and every stage that executes artifact content. Separating jobs does not create isolation when the second blindly trusts output from the first. Digests and attestations help bind artifacts to the expected build.
1
2
sha256sum build.tar.gz
# Record producer run ID, commit SHA and artifact digest before deployment.
npm, PyPI and NuGet Supply Chain Attacks
Installing a dependency means executing a trust decision based on name, version, maintainer and registry. Package managers resolve versions and may execute lifecycle scripts or load code during build and installation. Typosquatting, maintainer takeover, compromised tokens, dependency confusion and malicious updates are different paths toward the same consumer. I review lockfiles, registry configuration, package provenance, install scripts and unpinned dependencies before any lab test. Blocking public registries entirely is not always practical. Private proxies, namespaces, lockfiles, 2FA and provenance reduce risk in layers.
1
2
3
npm ls --all
pip freeze
dotnet list package --include-transitive
Git Internals
git log shows history reachable from refs. The object database may still contain content that disappeared from every visible branch. Git uses blobs, trees, commits and refs. Packfiles compress objects without changing the model. Reflogs and unreachable objects may preserve secrets, removed source code and old files locally. I use rev-list, cat-file, fsck and reflog to understand the repository before searching for sensitive strings. An unreachable object may disappear after garbage collection. Evidence should record the object hash and origin while it still exists.
1
2
3
git rev-list --objects --all
git fsck --full --unreachable
git reflog --all
Postman Collections, Environments and Secrets
A collection may carry URLs, tokens, scripts and variables sufficient to reproduce an entire integration without opening application source code. Postman collections describe requests and pre-request or test scripts. Environments store variables and may contain credentials. Exports, public workspaces and Git repositories may leak current values, API tokens and internal hostnames. I search for collection and environment files, review scripts and separate live secrets from placeholders before using any credential. A public collection is not automatically a vulnerability. Impact comes from active secrets, reachable internal endpoints or sensitive operations discovered through it.
1
find . -iname '*postman*' -o -iname '*.postman_collection.json' -o -iname '*.postman_environment.json'
References
- Security hardening for GitHub Actions
- Runner security
- OWASP Software Component Verification Standard
- Configuring registries
- Git internals: plumbing and porcelain
- Removing sensitive data
- Securing Jenkins
- Credentials Binding
- Artifact attestations
- Supply-chain Levels for Software Artifacts
- npm audit reports
- PyPI security
- Git internals: git objects