Documentation
¶
Index ¶
- Constants
- Variables
- func CleanMarkdown(input string) string
- func CommitAll(repo *git.Repository, message string, identity GitIdentity) (bool, error)
- func CommitDirChanges(dir, commitMessage string, identity GitIdentity) error
- func CopyDir(srcDir, dstDir string) error
- func CopyDirReplace(srcDir string, dstDir string, exclude ...string) error
- func CopyDirectory(srcDir, dstParentDir string) error
- func CopyFile(src, dst string, perm os.FileMode) error
- func DecryptAESGCM(encrypted string, key []byte) (string, error)
- func DecryptStoredSecret(encrypted string) (string, error)
- func EncryptAESGCM(plaintext string, key []byte) (string, error)
- func EnsureBareGitRepo(dir string) (*git.Repository, error)
- func EnsureGitRepo(dir string) (*git.Repository, error)
- func EscapeHTML(input string) string
- func FetchBareRepoFromOrigin(ctx context.Context, repoPath string) error
- func FindFileInGitRepo(repoPath, fileName string) (string, error)
- func GenerateID() int64
- func GetAESKey() []byte
- func GetAnalysisDebugDir(baseDir, analysisID, version string) string
- func GetAnalysisDir(baseDir, projectId string, workflow int64) string
- func GetAnalysisNodeCacheDir(workspaceDir string) string
- func GetAnalysisNodeDir(baseDir, projectId, scriptID string) string
- func GetAnalysisNodeEnvFile(workspaceDir string) string
- func GetAnalysisNodeOutputDir(workspaceDir string) string
- func GetCurrentTime() time.Time
- func GetDatasetDir(baseDir, projectId string, datasetID int64) string
- func GetProjectDir(baseDir, projectId string) string
- func GetProjectDocDir(baseDir, projectId string) string
- func GetProjectLiteratureDir(baseDir, projectId, literatureID string) string
- func GetProjectReportDir(baseDir, projectId, reportID string) string
- func GetScriptDir(baseDir, projectId string) string
- func GetScriptFile(baseDir, projectID, scriptType, scriptID string) (string, string, error)
- func GetScriptFileDir(baseDir, projectId, scriptID string) string
- func GetStoreDir(baseDir string) string
- func GetWorkflowDir(baseDir, projectId string) string
- func GetWorkflowFileDir(baseDir, projectId, workflowID string) string
- func GetWorkflowOrScriptStoreDir(baseDir, id string) string
- func InitSnowflake(workerID int64) error
- func IsPublicIP(ip net.IP) bool
- func IsSSRFWhitelisted(hostname string) bool
- func IsSystemProxy(host string) bool
- func IsValidImageURL(url string) bool
- func IsValidURL(url string) bool
- func NewSSRFSafeHTTPClient(config SSRFSafeHTTPClientConfig) *http.Client
- func PushDirToRepo(ctx context.Context, sourceDir, targetRepoPath string) error
- func ReadFileFromGitRepo(repoPath, relPath string) ([]byte, error)
- func RelPath(baseDir, path string) string
- func RelStoreDir(id string) string
- func ResolveConfiguredPath(configuredPath string, defaultRelativeToExecutable string) (string, error)
- func ResolveExternalPath(relativePath string) (string, error)
- func ResolveImageDir(baseDir string) string
- func ResolvePath(baseDir, path string) string
- func SSRFSafeDialContext(ctx context.Context, network, addr string) (net.Conn, error)
- func SafeFileName(fileName string) (string, error)
- func SafeObjectKey(objectKey string) error
- func SafePathUnderBase(baseDir, filePath string) (string, error)
- func SanitizeForDisplay(input string) string
- func SanitizeForLog(input string) string
- func SanitizeForLogArray(input []string) []string
- func SanitizeHTML(input string) string
- func SyncWorktreeFromRepo(ctx context.Context, targetDir, srcRepoPath string) error
- func ValidateInput(input string) (string, bool)
- func ValidateStdioArgs(args []string) error
- func ValidateStdioCommand(command string) error
- func ValidateStdioConfig(command string, args []string, envVars map[string]string) error
- func ValidateStdioEnvVars(envVars map[string]string) error
- func ValidateURLForSSRF(rawURL string) error
- type AnalysisLayout
- type GitIdentity
- type GitSyncState
- type NodeLayout
- type SSRFSafeHTTPClientConfig
Constants ¶
const EncPrefix = "enc:v1:"
EncPrefix marks a string as AES-256-GCM encrypted
Variables ¶
var AllowedStdioCommands = map[string]bool{ "uvx": true, "npx": true, }
AllowedStdioCommands defines the whitelist of allowed commands for MCP stdio transport These are the standard MCP server launchers that are considered safe
var DangerousArgPatterns = []*regexp.Regexp{ regexp.MustCompile(`(?i)^-c$`), regexp.MustCompile(`(?i)^--command$`), regexp.MustCompile(`(?i)^-e$`), regexp.MustCompile(`(?i)^--eval$`), regexp.MustCompile(`(?i)[;&|]`), regexp.MustCompile(`(?i)\$\(`), regexp.MustCompile("(?i)`"), regexp.MustCompile(`(?i)>\s*[/~]`), regexp.MustCompile(`(?i)<\s*[/~]`), regexp.MustCompile(`(?i)^/bin/`), regexp.MustCompile(`(?i)^/usr/bin/`), regexp.MustCompile(`(?i)^/sbin/`), regexp.MustCompile(`(?i)^/usr/sbin/`), regexp.MustCompile(`(?i)^\.\./`), regexp.MustCompile(`(?i)/\.\./`), regexp.MustCompile(`(?i)^(bash|sh|zsh|ksh|csh|tcsh|fish|dash)$`), regexp.MustCompile(`(?i)^(curl|wget|nc|netcat|ncat)$`), regexp.MustCompile(`(?i)^(rm|dd|mkfs|fdisk)$`), }
DangerousArgPatterns contains patterns that indicate potentially dangerous arguments
var DangerousEnvVarPatterns = []*regexp.Regexp{ regexp.MustCompile(`(?i)^LD_PRELOAD$`), regexp.MustCompile(`(?i)^LD_LIBRARY_PATH$`), regexp.MustCompile(`(?i)^DYLD_`), regexp.MustCompile(`(?i)^PATH$`), regexp.MustCompile(`(?i)^PYTHONPATH$`), regexp.MustCompile(`(?i)^NODE_OPTIONS$`), regexp.MustCompile(`(?i)^BASH_ENV$`), regexp.MustCompile(`(?i)^ENV$`), regexp.MustCompile(`(?i)^SHELL$`), }
DangerousEnvVarPatterns contains patterns for dangerous environment variable names or values
var ErrEncryptedDataMissingKey = errors.New("encrypted data found but SYSTEM_AES_KEY is not set or has wrong length")
ErrEncryptedDataMissingKey is returned by DecryptStoredSecret when the value carries the enc:v1: prefix but no AES key is available to decrypt it. This signals an operator misconfiguration (typically a rotated or unset SYSTEM_AES_KEY) and must propagate so the system fails loudly instead of silently using ciphertext as a credential.
var ErrSSRFRedirectBlocked = fmt.Errorf("redirect blocked: target URL failed SSRF validation")
ErrSSRFRedirectBlocked is returned when a redirect target is blocked due to SSRF protection
Functions ¶
func CommitAll ¶ added in v0.1.4
func CommitAll(repo *git.Repository, message string, identity GitIdentity) (bool, error)
CommitAll 把仓库工作区的全部变更(新增/修改/删除)暂存并提交为一个新的 commit。
- 工作区无变更时返回 (false, nil),不会产生空提交;
- message 为空时使用默认文案 "update repository";
- identity 的 Name/Email 必须非空(调用方应通过 config.ResolveGitIdentity 取值, 未配置 git 段时会回退到默认身份)。
仓库首次提交(HEAD 尚未出生)也能正常工作:会在当前分支上创建根提交。
func CommitDirChanges ¶ added in v0.1.4
func CommitDirChanges(dir, commitMessage string, identity GitIdentity) error
CommitDirChanges 确保 dir 是一个 git 仓库(不存在则初始化,默认分支 main), 并把当前工作区改动提交为一个 commit(无变更时不会产生空提交)。
这是脚本/工作流导出目录共用的提交步骤:写侧(Codec 的 WriteScriptFiles / WriteWorkflowFiles)落盘后调用它,保证目录内容始终有对应的 git 提交, 供后续 PushDirToRepo 推送到 store。
func CopyDir ¶
CopyDir copies the contents of srcDir into dstDir, preserving the directory structure. dstDir is created if it does not exist. Existing files in dstDir may be overwritten.
func CopyDirReplace ¶ added in v0.1.4
CopyDirReplace 用 srcDir 的内容完全替换 dstDir(先删除 dstDir 再重建后拷贝)。
用途是导出/安装两侧的目录快照与还原,例如把脚本目录快照进 workflow 目录, 或把 store 同步回来的脚本目录还原到本地脚本目录。
exclude 里的名称在任意层级都会被跳过(目录则整棵跳过),例如脚本目录自身的 ".git": 把脚本快照进 workflow 目录时不能把脚本仓库一起塞进去。
srcDir 不存在时视为无可拷贝内容,直接返回 nil(导出时脚本尚未落盘不应让整次保存失败)。
func CopyDirectory ¶
CopyDirectory copies the entire srcDir directory (not just its contents) into dstParentDir. For example, CopyDirectory("/a/foo", "/b") creates /b/foo/ with all files/dirs from /a/foo.
func DecryptAESGCM ¶
DecryptAESGCM decrypts an AES-256-GCM encrypted string. If the string lacks the enc:v1: prefix, it's treated as legacy plaintext and returned as-is.
func DecryptStoredSecret ¶
DecryptStoredSecret decrypts a value loaded from the database with strict error propagation. Use this from GORM Scan/AfterFind hooks for any field that stores AES-encrypted secrets (API keys, passwords, app secrets).
Behaviour:
- empty input -> empty output, no error.
- no enc:v1: prefix -> returned as-is (legacy plaintext column), no error.
- has enc:v1: prefix and SYSTEM_AES_KEY is missing or wrong length -> returns ErrEncryptedDataMissingKey. Callers MUST propagate this so the load fails loudly instead of silently surfacing ciphertext as a credential.
- has enc:v1: prefix and key is set -> decrypts, returns any decryption error verbatim (e.g. base64 decode failure, GCM auth tag mismatch from a rotated key).
The previous lenient pattern (`if decrypted, err := ...; err == nil { ... }`) hid the rotated-key case and caused the encrypted ciphertext to be sent upstream as the actual API key, surfacing as 401/403 from third-party vendors. Always prefer this helper over calling DecryptAESGCM directly when loading from the database.
func EncryptAESGCM ¶
EncryptAESGCM encrypts plaintext with AES-256-GCM. Returns the original string if empty, already encrypted, or key is nil.
func EnsureBareGitRepo ¶ added in v0.1.4
func EnsureBareGitRepo(dir string) (*git.Repository, error)
EnsureBareGitRepo 保证 dir 是一个裸(bare)git 仓库并返回它,用作本地推送目标:
- dir 不存在:初始化裸仓库(默认分支 main)
- dir 已是裸仓库:直接复用,保留历史,便于多次 publish 增量推送
- dir 存在但不是裸仓库(例如旧版本留下的普通文件产物):清空后重新初始化
DefaultBranch 固定为 main,避免受本机 git 默认分支配置影响。
func EnsureGitRepo ¶ added in v0.1.4
func EnsureGitRepo(dir string) (*git.Repository, error)
EnsureGitRepo 在 dir 目录下初始化一个非裸(带工作区)的 git 仓库。 若 dir 下已存在仓库,则直接打开并复用,不会清空已有历史。 DefaultBranch 固定为 main,避免受本机 git 默认分支配置影响。
func FetchBareRepoFromOrigin ¶ added in v0.1.4
FetchBareRepoFromOrigin 把裸仓库更新到 origin 的最新提交。
裸仓库没有工作区,Worktree().Pull 不可用(会报 worktree not available),因此等价实现为: force fetch origin(刷新 refs/remotes/origin/*)后,把 HEAD 指向的本地分支指向同名远端分支。
远端没有新提交时返回 git.NoErrAlreadyUpToDate,与 Worktree().Pull 的语义一致, 调用方可用 stderrs.Is(err, git.NoErrAlreadyUpToDate) 判断“已是最新”。
func FindFileInGitRepo ¶ added in v0.1.4
FindFileInGitRepo 在仓库 HEAD 提交的文件树里按文件名查找文件,返回仓库内的相对路径 ("/" 分隔),可直接交给 ReadFileFromGitRepo 读取。
查找口径与旧的文件系统查找保持一致:根目录优先,其次按路径顺序查找(忽略大小写)。 同时支持裸仓库与普通仓库:store 是裸仓库,没有工作区文件,只能从 git 对象里找。
func GenerateID ¶
func GenerateID() int64
func GetAESKey ¶
func GetAESKey() []byte
GetAESKey reads the 32-byte AES key from SYSTEM_AES_KEY env. Returns nil if not set or not exactly 32 bytes.
func GetAnalysisDebugDir ¶
func GetAnalysisDir ¶
func GetAnalysisNodeCacheDir ¶ added in v0.1.4
func GetAnalysisNodeDir ¶
func GetAnalysisNodeEnvFile ¶ added in v0.1.4
func GetAnalysisNodeOutputDir ¶ added in v0.1.4
func GetCurrentTime ¶
func GetDatasetDir ¶ added in v0.1.4
func GetProjectDir ¶
func GetProjectDocDir ¶
func GetProjectLiteratureDir ¶ added in v0.1.2
func GetProjectReportDir ¶ added in v0.1.2
func GetScriptDir ¶
func GetScriptFile ¶
func GetScriptFileDir ¶
func GetStoreDir ¶ added in v0.1.4
func GetWorkflowDir ¶
func GetWorkflowFileDir ¶ added in v0.1.4
func GetWorkflowOrScriptStoreDir ¶ added in v0.1.4
GetWorkflowOrScriptStoreDir resolves the absolute store directory of a published workflow/script (or of a downloaded remote repo whose id is "<owner>/<repo>").
The single source of truth for the relative layout is RelStoreDir; keep both in sync by always deriving one from the other.
func InitSnowflake ¶
func IsPublicIP ¶
IsPublicIP returns true if the IP is safe for outbound fetch (not private, loopback, link-local, etc.). Used for DNS pinning: after resolving a hostname we pick the first public IP and pin all requests to it.
func IsSSRFWhitelisted ¶
IsSSRFWhitelisted checks whether the given hostname (or IP string) is covered by the SSRF_WHITELIST environment variable.
func NewSSRFSafeHTTPClient ¶
func NewSSRFSafeHTTPClient(config SSRFSafeHTTPClientConfig) *http.Client
NewSSRFSafeHTTPClient creates an HTTP client that validates redirect targets against SSRF protections. This prevents SSRF attacks via HTTP redirects where an attacker's server redirects to internal services.
func PushDirToRepo ¶ added in v0.1.4
PushDirToRepo 把 sourceDir 仓库的当前分支推送到 targetRepoPath 对应的本地仓库。
等价于 `git remote add/ set-url <origin> <targetRepoPath>` 后再 `git push`: 每次调用都会把名为 origin 的 remote 指向最新的 targetRepoPath(base_dir 变更后 无需手工改 remote 配置),并以 force 方式推送,使目标仓库与源分支内容完全一致。
源仓库没有 commit(HEAD 未出生)时返回错误,调用方应提示先保存。
func ReadFileFromGitRepo ¶ added in v0.1.4
ReadFileFromGitRepo 读取仓库 HEAD 提交中指定相对路径("/" 分隔)的文件内容。
同时支持裸仓库与普通仓库:本地发布的 store 是裸仓库,没有工作区文件,封面图等 只能从 git 对象里读取。
func RelPath ¶ added in v0.1.4
RelPath converts an absolute storage path into the relative form that gets persisted in the database.
Persisted storage paths are always relative to storage.base_dir so that relocating the storage root only requires copying the directory tree, not rewriting database rows. Paths that are already relative (or that live outside baseDir, which we do not own) are returned untouched so we never silently mangle data.
func RelStoreDir ¶ added in v0.1.4
RelStoreDir is the base_dir-relative location of a store entry.
func ResolveConfiguredPath ¶
func ResolveConfiguredPath(configuredPath string, defaultRelativeToExecutable string) (string, error)
ResolveConfiguredPath resolves storage path by this rule: - when configuredPath is empty, use executableDir/defaultRelativeToExecutable - otherwise, use configuredPath directly.
func ResolveExternalPath ¶
ResolveExternalPath resolves project external file paths. If BRAVE_CONFIG_DIR is set, the path is resolved relative to it; otherwise, it is resolved relative to current working directory.
func ResolveImageDir ¶
func ResolvePath ¶ added in v0.1.4
ResolvePath turns a persisted path back into an absolute path.
It is deliberately tolerant: an empty value stays empty and legacy rows that still hold an absolute path are returned as-is, so mixed old/new data keeps working while the migration is rolling out.
func SSRFSafeDialContext ¶
SSRFSafeDialContext is a custom dial function that validates the resolved IP addresses before establishing a connection. This provides an additional layer of SSRF protection against DNS rebinding attacks during the connection phase.
func SafeFileName ¶
SafeFileName 校验并返回安全的“仅文件名”部分,防止路径遍历。 仅保留最后一个路径成分,禁止 ".."、空名或仅含点,用于 SaveBytes 等场景。
func SafeObjectKey ¶
SafeObjectKey 校验对象存储的 key(如 COS/MinIO objectName),禁止包含 ".." 等路径遍历
func SafePathUnderBase ¶
SafePathUnderBase 校验 filePath 是否落在 baseDir 下,防止路径遍历(如 ../../)。 返回规范化的绝对路径;若路径逃逸出 baseDir 则返回错误。
func SanitizeForLog ¶
SanitizeForLog 清理日志输入,防止日志注入攻击 日志注入攻击是指攻击者通过在输入中插入换行符和其他控制字符, 伪造日志条目,可能导致日志分析工具误判或隐藏恶意活动
func SanitizeForLogArray ¶
SanitizeForLogArray 清理日志输入数组,防止日志注入攻击
func SyncWorktreeFromRepo ¶ added in v0.1.4
SyncWorktreeFromRepo 让 targetDir 的工作区与 srcRepoPath 仓库保持一致:
- targetDir 已是 git 仓库:从 srcRepoPath fetch 后 reset --hard,覆盖本地改动
- targetDir 不存在或为空目录:从 srcRepoPath clone(非裸克隆,带工作区)
- targetDir 存在已有内容但不是 git 仓库(旧版本安装留下的普通文件目录): 就地初始化仓库后再 reset --hard,语义同"覆盖本地"
srcRepoPath 既可以是裸仓库(publish 产物),也可以是普通工作区仓库。
func ValidateStdioArgs ¶
ValidateStdioArgs validates the arguments for MCP stdio transport Returns an error if any argument contains dangerous patterns
func ValidateStdioCommand ¶
ValidateStdioCommand validates the command for MCP stdio transport Returns an error if the command is not in the whitelist or contains dangerous patterns
func ValidateStdioConfig ¶
ValidateStdioConfig performs comprehensive validation of stdio configuration This should be called before creating or executing any stdio-based MCP client
func ValidateStdioEnvVars ¶
ValidateStdioEnvVars validates environment variables for MCP stdio transport Returns an error if any env var name or value is dangerous
func ValidateURLForSSRF ¶
ValidateURLForSSRF is the centralised entry-point that all handlers should call to validate a user-supplied URL. It first checks the SSRF_WHITELIST; whitelisted hosts skip the full isSSRFSafeURL check.
rawURL may be a full URL ("https://example.com/v1") or a bare host/host:port (for cases like ReconnectDocReader). If a scheme is missing the function prepends "https://" before parsing so that net/url can extract the host.
Returns nil when the URL is safe, or an error describing the problem.
Types ¶
type AnalysisLayout ¶ added in v0.1.4
type AnalysisLayout struct {
// Dir is the analysis root directory.
Dir string
// ParamsPath is the request params payload written at save time.
ParamsPath string
// CommandPath is the generated run.sh path.
CommandPath string
// CommandLogPath is the captured run log path.
CommandLogPath string
// TraceFile is the nextflow trace log path.
TraceFile string
// WorkflowLogFile is the nextflow workflow log path.
WorkflowLogFile string
// ExecutorLogFile is the nextflow executor log path.
ExecutorLogFile string
}
AnalysisLayout is the on-disk contract of a single analysis.
Every artifact path an analysis row exposes is derived here, so writers and readers cannot drift apart.
func AnalysisLayoutFor ¶ added in v0.1.4
func AnalysisLayoutFor(workspaceDir string) AnalysisLayout
AnalysisLayoutFor derives the analysis layout from its root directory. An empty root yields a zero layout instead of dangling filenames.
type GitIdentity ¶ added in v0.1.4
GitIdentity 是 git 提交时使用的身份信息,对应 config.GitConfig。
type GitSyncState ¶ added in v0.1.4
type GitSyncState struct {
LocalDir string `json:"local_dir,omitempty"`
StoreDir string `json:"store_dir,omitempty"`
// LocalInitialized 本地目录是否已是一个 git 仓库(脚本/工作流的版本目录)。
LocalInitialized bool `json:"local_initialized"`
// StoreInitialized 发布目标是否已是一个 git 仓库(即是否已发布过)。
StoreInitialized bool `json:"store_initialized"`
// LocalCommit 本地工作区 HEAD 的提交 hash(尚无提交时为空)。
LocalCommit string `json:"local_commit,omitempty"`
// StoreCommit 发布目标 HEAD 的提交 hash(未发布时为空)。
StoreCommit string `json:"store_commit,omitempty"`
// LocalDirty 本地工作区存在未提交改动(新增/修改/删除,含未跟踪文件)。
LocalDirty bool `json:"local_dirty"`
// LocalAhead 本地 HEAD 领先 store:存在已提交但未发布的改动。
LocalAhead bool `json:"local_ahead"`
// StoreAhead store 领先本地:远端有本地没有的提交(需要 install/同步)。
StoreAhead bool `json:"store_ahead"`
// HasLocalChanges 本地有未发布改动 = 未提交改动 或 本地领先 store。
HasLocalChanges bool `json:"has_local_changes"`
// HasStoreChanges store 有本地未同步的提交(远端有更新)。
HasStoreChanges bool `json:"has_store_changes"`
// InSync 本地干净且两侧 commit 一致。
InSync bool `json:"in_sync"`
}
GitSyncState 描述「本地工作目录仓库」与「发布目标仓库(store 裸仓库)」之间的同步状态。
设计取舍:状态完全由磁盘上的 git 元数据通过 go-git 实时推导,不落库。 原因:
- “本地有更新”天然包含未提交的工作区改动(脚本文件被直接编辑), 任何 DB 字段都无法表达;
- SaveScript / PublishScript / InstallScript 三个写入口都要同步维护 commit_id, 而 EnsureBareGitRepo 会重建非裸目录、InstallScript 会 reset --hard, 落库字段极易与真实仓库状态漂移;
- 本地目录是小仓库,Worktree.Status 与 HEAD 读取成本很低,无需缓存。
func ReadGitSyncState ¶ added in v0.1.4
func ReadGitSyncState(localDir, storeDir string) GitSyncState
ReadGitSyncState 读取 localDir(脚本/工作流目录)与 storeDir(发布裸仓库)的同步状态。
两个目录都可以不存在或不是 git 仓库:此时返回零值语义的状态(不报错), 便于接口在“从未保存/从未发布”的情况下也能安全返回。
type NodeLayout ¶ added in v0.1.4
type NodeLayout struct {
// Dir is the node root directory.
Dir string
// OutputDir receives produced artifacts.
OutputDir string
// CacheDir holds intermediate cache artifacts.
CacheDir string
// ParamsPath is the generated params.json path.
ParamsPath string
// CommandPath is the generated run.sh path.
CommandPath string
// LogPath is the captured command log path.
LogPath string
}
NodeLayout is the on-disk contract of a single analysis node.
func NodeLayoutFor ¶ added in v0.1.4
func NodeLayoutFor(workspaceDir string) NodeLayout
NodeLayoutFor derives the node layout from its root directory. An empty root yields a zero layout instead of dangling filenames.
type SSRFSafeHTTPClientConfig ¶
type SSRFSafeHTTPClientConfig struct {
Timeout time.Duration
MaxRedirects int
DisableKeepAlives bool
DisableCompression bool
}
SSRFSafeHTTPClientConfig contains configuration for the SSRF-safe HTTP client
func DefaultSSRFSafeHTTPClientConfig ¶
func DefaultSSRFSafeHTTPClientConfig() SSRFSafeHTTPClientConfig
DefaultSSRFSafeHTTPClientConfig returns the default configuration