Files
wehub-resource-sync bf9395e022
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:22:54 +08:00

126 lines
4.0 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package apps
import (
"path/filepath"
"strings"
)
// isSensitiveRelPath reports whether a relative path inside the candidate
// manifest is a well-known env / credential file that should not ship to a
// public-internet share URL. The check is path-element-wise (each
// "/"-delimited segment is inspected) so credential files nested under
// arbitrary subdirectories are still caught.
//
// Used by +html-publish: dry-run AND Execute both block by default when any
// candidate matches. Pass --allow-sensitive to override (legitimate cases:
// a documentation site shipping example credential files on purpose).
//
// Scope is intentionally narrow — only files that conventionally hold API
// tokens or service credentials, not the broader "anything cryptographic"
// surface. SSH private keys, generic *.pem / *.key, and SCM internals are
// out of scope here; if they leak it's a separate problem to address.
func isSensitiveRelPath(rel string) bool {
if rel == "" {
return false
}
parts := strings.Split(rel, "/")
for i, p := range parts {
switch {
case p == ".env" || strings.HasPrefix(p, ".env."):
return true
case p == ".npmrc":
return true
case p == ".netrc":
return true
case p == ".git-credentials":
return true
}
if i == 0 {
continue
}
parent := parts[i-1]
switch parent {
case ".aws":
if p == "credentials" {
return true
}
case ".docker":
if p == "config.json" {
return true
}
case ".kube":
if p == "config" {
return true
}
}
}
return false
}
// hasParentAnchoredCredentialPair scans a "/"-delimited path for the
// cloud-SDK matchers that depend on a conventional parent dir:
// .aws/credentials, .docker/config.json, .kube/config. The leaf-name
// matchers (.env / .npmrc / ...) intentionally do NOT run here, so callers
// can probe a path that includes surrounding root context without risking
// a leaf-rule false-positive on the context segment itself (e.g. a literal
// ".env" directory somewhere in --path's ancestry).
func hasParentAnchoredCredentialPair(path string) bool {
parts := strings.Split(path, "/")
for i := 1; i < len(parts); i++ {
switch parts[i-1] {
case ".aws":
if parts[i] == "credentials" {
return true
}
case ".docker":
if parts[i] == "config.json" {
return true
}
case ".kube":
if parts[i] == "config" {
return true
}
}
}
return false
}
// isSensitiveCandidate is the call-site wrapper used by +html-publish.
//
// Two passes:
//
// 1. Scan RelPath with the full matcher (isSensitiveRelPath). Handles the
// common in-tree case (e.g. ./site/.env, ./dist/.docker/config.json).
// 2. Re-probe at the boundary between rootPath and the candidate, using
// ONLY hasParentAnchoredCredentialPair. walker strips the root segment
// via filepath.Rel, so when --path is itself the conventional parent
// dir (e.g. ./.aws) RelPath comes back as a bare "credentials" and
// step 1 has no parent to anchor on. Re-prepending the root's basename
// — or, for the single-file form, the parent dir's basename of
// rootPath — exposes the missing segment. Leaf matchers are NOT re-run
// in this pass, so an ancestor like /home/alice/.env/dist can't
// false-positive every file beneath it just because ".env" appears in
// the root context.
//
// Pure string-level reasoning over rootPath — no filesystem access, no
// reliance on cwd — so it composes with the project's fileio sandbox and
// stays inside the shortcuts-layer constraint against direct fs lookups.
func isSensitiveCandidate(rootPath string, c htmlPublishCandidate) bool {
if isSensitiveRelPath(c.RelPath) {
return true
}
for _, ctx := range []string{filepath.Base(rootPath), filepath.Base(filepath.Dir(rootPath))} {
switch ctx {
case "", ".", "..", "/":
continue
}
if hasParentAnchoredCredentialPair(filepath.ToSlash(filepath.Join(ctx, c.RelPath))) {
return true
}
}
return false
}