Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions LOCAL-DATA-BOUNDARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Local Data Boundary

This procedure protects local-only files from being accidentally included in
repository scans or sent to a remote model, CI service, issue, pull request,
or artifact store.

## What is protected

The following must remain outside public repositories and scanner inputs:

- Alibaba internal source, documents, exports, logs, screenshots, and
credentials;
- `.env` files, private keys, certificates, cloud credentials, and local
provider configuration;
- browser profiles, chat exports, SSH configuration, and unrelated worktrees;
- generated runtime data, databases, caches, and local agent state.

## Safe local procedure

From the repository that is actually being inspected, run:

```bash
scripts/bytefolk-scrub-env.sh ruby scripts/bytefolk-local-boundary-check.rb .
```

The check is local-only. The wrapper uses an environment allowlist so GitHub,
cloud, package, and model credentials are not inherited by the validator. The
validator uses Git metadata and local `git grep`, prints counts rather than
matching values, and exits non-zero when it finds a sensitive filename, a
tracked credential-shaped value, or a tracked symlink. It does not call a
network service, an LLM, or GitHub.

Use an explicit repository path. Never pass the workspace parent, `$HOME`,
the filesystem root, or a directory containing multiple repositories to a
scanner. In particular, do not run `strix --target .` from the ByteFolk
workspace root.

For a clean, committed-tree-only inspection, first pass the boundary check,
then create a disposable checkout from the exact commit under review. Do not
copy `.env*`, credentials, keys, certificates, runtime state, or unrelated
files into that checkout. A local change that has not been committed should
be reviewed with a local diff or a local scanner; it should not be uploaded to
an external service by default.

## Remote boundary

- A GitHub-hosted job sees repository content only after it is pushed or
otherwise supplied to the job. It cannot read files sitting on a developer's
computer.
- Secrets are not safe to expose to arbitrary build, test, or AI steps. Keep
them in the smallest possible job, and do not place them in command-line
arguments, logs, artifacts, issue text, or pull-request comments.
- Do not use `pull_request_target` to check out or execute an untrusted pull
request. Fork pull requests must not receive model-provider or deployment
credentials.
- If an AI or cloud scanner is used, set its telemetry and data-sharing
options explicitly, use an approved endpoint, and record the data boundary
in the validation ledger.
- Do not assume that an environment variable is hidden because it is masked in
CI logs. A local child process can still inherit it. Run deterministic local
validators through `scripts/bytefolk-scrub-env.sh`, and never run arbitrary
repository scripts with a GitHub or provider credential in the environment.

## If exposure is suspected

Stop the scan or upload, preserve only redacted evidence, revoke and rotate
the affected credential, and report the incident privately through the
affected repository's Security tab. Removing a leaked file from the latest
commit is not sufficient.
57 changes: 57 additions & 0 deletions SECURITY-BASELINE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# ByteFolk Security Baseline

This is the organization baseline for public ByteFolk repositories. It is a
defense-in-depth control set; a green check is evidence about the checked
commit and scope, not a guarantee that the application is secure.

## Adoption order

1. Enable GitHub Secret Scanning and Push Protection for every public
repository. Restrict bypass to a small maintainer group and rotate any
credential that was exposed, even when the alert is later dismissed.
2. Enable Dependabot alerts and security updates. Configure version updates
for the repository's package managers and for GitHub Actions.
3. Add the `ByteFolk Security Baseline` workflow template. Set the CodeQL
language matrix to the languages actually present in the repository.
4. Add the `ByteFolk Scorecard` workflow template on the default branch and a
scheduled run.
5. Require the security checks, the repository CI, the linked issue, and the
applicable CODEOWNER approval in the repository ruleset.
6. Add artifact attestations to release workflows and verify them before
publishing or consuming release assets.

## Non-negotiable workflow rules

- Pin every third-party Action to a full 40-character commit SHA. Keep the
human-readable release tag in a same-line comment so Dependabot can update
it.
- Set `permissions: contents: read` at workflow scope and grant additional
permissions only to the individual job that needs them.
- Do not use `pull_request_target` to check out or execute pull-request code.
- Do not expose release, cloud, package-publishing, OIDC, or model-provider
credentials to tests or scanners that execute repository code.
- Do not use mutable Action refs, Docker `latest` tags, remote install pipes,
or unreviewed downloaded binaries in a security gate.
- Optional third-party workflow linters such as zizmor must be reviewed
separately; they are not part of the blocking baseline until their complete
download and execution chain is approved.
- Treat a scanner execution error, incomplete result, or missing artifact as a
failed gate. Do not convert it into a warning silently.

## Scope boundary

GitHub Actions run on a GitHub-hosted runner and receive only the repository
contents checked out by the workflow plus explicitly supplied variables and
secrets. They do not have access to the maintainer's local home directory.
Local scanners are a separate trust boundary. Before using one, follow
[`LOCAL-DATA-BOUNDARY.md`](LOCAL-DATA-BOUNDARY.md) and run the local boundary
check from inside the intended repository.

## AI and dynamic scanners

AI-driven or actively probing tools such as Strix are optional additions, not
part of this baseline. If one is adopted later, run it in an isolated,
ephemeral staging environment with synthetic data, no release credentials,
an approved model endpoint, telemetry disabled where policy requires it, and
an explicit egress allowlist. It must not run from the workspace root or from
a directory containing unrelated local repositories.
86 changes: 86 additions & 0 deletions scripts/bytefolk-local-boundary-check.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env ruby

require "open3"
require "set"

GENERATED_PATH_SEGMENTS = %w[node_modules .venv venv dist build .next .cache coverage]

def run_git!(repo, *args)
stdout, _stderr, status = Open3.capture3("git", "-C", repo, *args)
return stdout if status.success?

abort "local boundary check could not inspect the repository (git #{args.first} failed)"
end

def sensitive_path?(path)
normalized = path.downcase
segments = normalized.split("/")
basename = File.basename(normalized)

return false if segments.any? { |segment| GENERATED_PATH_SEGMENTS.include?(segment) }
return false if %w[.env.example .env.sample].include?(basename)

normalized.match?(%r{(^|/)(credentials?|secrets?|private|id_rsa|id_ed25519)(/|$)}) ||
segments.any? { |segment| segment.start_with?(".env") && !segment.end_with?(".example") && !segment.end_with?(".sample") } ||
%w[.npmrc .pypirc .netrc].include?(basename) ||
normalized.match?(%r{\.(pem|key|p12|pfx|jks|keystore|tfstate|tfvars)(\.|$)})
end

abort "usage: ruby scripts/bytefolk-local-boundary-check.rb REPOSITORY" unless ARGV.length == 1

begin
repo = File.realpath(ARGV.fetch(0))
rescue SystemCallError
abort "local boundary check: repository path does not exist"
end

root = run_git!(repo, "rev-parse", "--show-toplevel").strip
root = File.realpath(root)
abort "local boundary check: pass one repository, not its parent" unless root == repo
abort "local boundary check: refusing the filesystem root" if root == "/"

tracked = run_git!(root, "ls-files", "-z").split("\0").reject(&:empty?)
untracked = run_git!(root, "ls-files", "--others", "--exclude-standard", "-z").split("\0").reject(&:empty?)
ignored = run_git!(root, "ls-files", "--others", "--ignored", "--exclude-standard", "-z").split("\0").reject(&:empty?)

tracked_sensitive = tracked.count { |path| sensitive_path?(path) }
untracked_sensitive = (untracked + ignored).count { |path| sensitive_path?(path) }

symlink_count = run_git!(root, "ls-files", "-s", "-z").split("\0").count do |record|
record.start_with?("120000 ")
end

suspicious_patterns = [
"-----BEGIN (RSA|EC|OPENSSH|DSA|PGP) PRIVATE KEY-----",
"\\b(AKIA|ASIA)[0-9A-Z]{16}\\b",
"\\b(LTAI|AKID)[A-Za-z0-9]{12,}\\b",
"\\bgh[pousr]_[A-Za-z0-9_]{20,}\\b",
"\\bgithub_pat_[A-Za-z0-9_]{20,}\\b",
"\\bxox[baprs]-[A-Za-z0-9-]{20,}\\b"
]

suspicious_files = Set.new
suspicious_patterns.each do |pattern|
stdout, _stderr, status = Open3.capture3(
"git", "-C", root, "grep", "-I", "-i", "-l", "-E", pattern, "--"
)
suspicious_files.merge(stdout.split("\n")) if status.success?
end

puts "repository_files=#{tracked.length}"
puts "untracked_files=#{untracked.length}"
puts "ignored_files=#{ignored.length}"
puts "tracked_sensitive_names=#{tracked_sensitive}"
puts "untracked_or_ignored_sensitive_names=#{untracked_sensitive}"
puts "tracked_symlinks=#{symlink_count}"
puts "suspicious_content_files=#{suspicious_files.length}"

violations = tracked_sensitive + untracked_sensitive + symlink_count + suspicious_files.length
if violations.zero?
puts "result=PASS"
exit 0
end

puts "result=HOLD"
puts "details=suppressed; inspect locally without copying values into logs"
exit 1
25 changes: 25 additions & 0 deletions scripts/bytefolk-scrub-env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash

set -euo pipefail

if [[ "$#" -eq 0 ]]; then
echo "usage: scripts/bytefolk-scrub-env.sh COMMAND [ARG ...]" >&2
exit 2
fi

# Use an allowlist instead of trying to guess every provider-specific secret
# variable. This wrapper is for local scanners and validators that do not need
# credentials. It does not grant filesystem isolation; keep the command inside
# one explicit repository and run the boundary check first.
safe_path="${PATH:-/usr/bin:/bin:/usr/sbin:/sbin}"
safe_lang="${LANG:-C}"
safe_lc_all="${LC_ALL:-C}"
safe_tmpdir="${TMPDIR:-/tmp}"

exec env -i \
PATH="$safe_path" \
LANG="$safe_lang" \
LC_ALL="$safe_lc_all" \
TMPDIR="$safe_tmpdir" \
GIT_TERMINAL_PROMPT=0 \
"$@"
49 changes: 49 additions & 0 deletions workflow-templates/bytefolk-scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: ByteFolk Scorecard

on:
push:
branches:
- main
schedule:
- cron: "43 3 * * 1"
workflow_dispatch:

permissions:
contents: read

jobs:
scorecard:
name: OpenSSF Scorecard
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
actions: read
pull-requests: read
security-events: write
id-token: write
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Run Scorecard analysis
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
with:
results_file: results.sarif
results_format: sarif
publish_results: true

- name: Upload Scorecard artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: scorecard-results
path: results.sarif
if-no-files-found: error
retention-days: 14

- name: Upload Scorecard results
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.4
with:
sarif_file: results.sarif
10 changes: 10 additions & 0 deletions workflow-templates/bytefolk-security.properties.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "ByteFolk Security Baseline",
"description": "Pinned CodeQL and dependency-review checks for ByteFolk repositories",
"iconName": "shield",
"categories": [
"Code scanning",
"Dependency management",
"Security"
]
}
68 changes: 68 additions & 0 deletions workflow-templates/bytefolk-security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: ByteFolk Security Baseline

on:
pull_request:
push:
branches:
- main
schedule:
- cron: "17 3 * * 1"
workflow_dispatch:

permissions:
contents: read

jobs:
dependency-review:
name: Dependency review
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Review dependency changes
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
fail-on-severity: high
fail-on-scopes: runtime
comment-summary-in-pr: never

codeql:
name: CodeQL (${{ matrix.language }})
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
actions: read
packages: read
security-events: write
strategy:
fail-fast: false
matrix:
# Change this list to the languages present in the repository.
language:
- javascript-typescript
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Initialize CodeQL
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.4
with:
languages: ${{ matrix.language }}
queries: security-extended

- name: Autobuild
uses: github/codeql-action/autobuild@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.4

- name: Analyze
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.4
with:
category: /language:${{ matrix.language }}