feat(security): security hardening, proxy support, and GL045 HTTP include warning
ci / vet, staticcheck, test, build (push) Failing after 2m32s
release / Build and publish release (push) Successful in 1m17s

Security fixes:
- Path traversal guard in include: local: — paths with ../ that escape
  the repo root are rejected instead of reading arbitrary host files
- HTTP timeout (30 s) on all fetcher requests to prevent indefinite hangs
- Response size cap (10 MiB) via io.LimitReader to prevent memory exhaustion
- Cache directory and file permissions tightened to 0700/0600
- LSP Content-Length cap (64 MiB) to guard against DoS from a malicious client

New feature:
- --proxy flag on check, graph, and lsp subcommands; also proxy: key in
  .glint.yml; overrides system HTTP_PROXY / HTTPS_PROXY env vars when set;
  cmdGraph and cmdLSP now also load .glint.yml for proxy/token/url fallbacks

New lint rule:
- GL045 (Warning): include: remote: using plain http:// instead of https://

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 21:52:57 +02:00
parent 4972adb213
commit f79c64cd44
22 changed files with 422 additions and 18 deletions
+32 -3
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"git.k3nny.fr/glint/internal/config"
"git.k3nny.fr/glint/internal/fetcher"
"git.k3nny.fr/glint/internal/lsp"
)
@@ -15,6 +16,7 @@ func cmdLSP(args []string) {
gitlabURL := fs.String("gitlab-url", "", "GitLab instance URL (overrides CI_SERVER_URL / GITLAB_URL)")
cacheDir := fs.String("cache-dir", "", "directory for caching fetched remote includes")
offline := fs.Bool("offline", false, "skip all network calls; serve only from --cache-dir")
proxy := fs.String("proxy", "", "HTTP proxy URL for remote includes and GitLab API calls (e.g. http://proxy:8080); overrides system proxy env vars")
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "glint %s\n\n", version)
fmt.Fprint(os.Stderr, `Start a Language Server Protocol server for .gitlab-ci.yml files.
@@ -42,6 +44,12 @@ Options:
Do not make any network calls; resolve only local includes.
Implies --cache-dir default (~/.cache/glint) when not set.
--proxy <URL>
HTTP proxy URL for remote includes and GitLab API calls
(e.g. http://proxy:8080). Overrides system proxy env vars
(HTTP_PROXY / HTTPS_PROXY). Also configurable via proxy: in
.glint.yml.
-h, --help
Print help
@@ -49,19 +57,40 @@ Examples:
glint lsp
glint lsp --token glpat-xxxx --cache-dir ~/.cache/glint
glint lsp --offline
glint lsp --proxy http://proxy.example.com:8080
`)
}
_ = fs.Parse(args)
// Load project config from the working directory (the project root from
// which the LSP server is launched). CLI flags take priority.
wd, _ := os.Getwd()
glintCfg, cfgErr := config.Load(wd)
if cfgErr != nil {
fmt.Fprintf(os.Stderr, "glint lsp: [warning] %s: %v\n", config.Filename, cfgErr)
}
fetcherToken := *token
if fetcherToken == "" {
fetcherToken = glintCfg.Token
}
fetcherURL := *gitlabURL
if fetcherURL == "" {
fetcherURL = glintCfg.URL
}
resolvedCacheDir := *cacheDir
if resolvedCacheDir == "" {
resolvedCacheDir = glintCfg.CacheDir
}
if resolvedCacheDir == "" {
resolvedCacheDir = defaultCacheDir()
}
if *offline && resolvedCacheDir == "" {
resolvedCacheDir = defaultCacheDir()
resolvedProxy := *proxy
if resolvedProxy == "" {
resolvedProxy = glintCfg.Proxy
}
cfg := fetcher.AutoConfig().WithOverrides(*gitlabURL, *token, resolvedCacheDir, *offline)
cfg := fetcher.AutoConfig().WithOverrides(fetcherURL, fetcherToken, resolvedCacheDir, *offline).WithProxy(resolvedProxy)
srv := lsp.New(os.Stdin, os.Stdout, cfg, version)
if err := srv.Run(); err != nil {
+29 -3
View File
@@ -119,6 +119,7 @@ func cmdCheck(args []string) {
gitlabURL := fs.String("gitlab-url", "", "GitLab instance URL (overrides CI_SERVER_URL / GITLAB_URL)")
cacheDir := fs.String("cache-dir", "", "directory to cache fetched remote includes (created if needed)")
offline := fs.Bool("offline", false, "skip all network calls; serve only from --cache-dir")
proxy := fs.String("proxy", "", "HTTP proxy URL for remote includes and GitLab API calls (e.g. http://proxy:8080); overrides system proxy env vars")
format := fs.String("format", "text", "output format: text, json, sarif, junit, github")
branch := fs.String("branch", "", "simulate a branch push (sets CI_COMMIT_BRANCH, …)")
tag := fs.String("tag", "", "simulate a tag push (sets CI_COMMIT_TAG, …)")
@@ -286,8 +287,12 @@ Examples:
if *offline && resolvedCacheDir == "" {
resolvedCacheDir = defaultCacheDir()
}
resolvedProxy := *proxy
if resolvedProxy == "" {
resolvedProxy = glintCfg.Proxy
}
cfg := fetcher.AutoConfig().WithOverrides(fetcherURL, fetcherToken, resolvedCacheDir, *offline)
cfg := fetcher.AutoConfig().WithOverrides(fetcherURL, fetcherToken, resolvedCacheDir, *offline).WithProxy(resolvedProxy)
p, err := model.Parse(path)
if err != nil {
@@ -448,6 +453,7 @@ func cmdGraph(args []string) {
gitlabURL := fs.String("gitlab-url", "", "GitLab instance URL (overrides CI_SERVER_URL / GITLAB_URL)")
cacheDir := fs.String("cache-dir", "", "directory to cache fetched remote includes (created if needed)")
offline := fs.Bool("offline", false, "skip all network calls; serve only from --cache-dir")
proxy := fs.String("proxy", "", "HTTP proxy URL for remote includes and GitLab API calls (e.g. http://proxy:8080)")
out := fs.String("out", "glint-out", "output directory for rendered graph files (pipeline mode)")
format := fs.String("format", "svg", "pipeline output format: svg, mermaid, or html")
fs.Usage = func() {
@@ -559,13 +565,34 @@ Examples:
return
}
path := fs.Arg(0)
rootDir := filepath.Dir(filepath.Clean(path))
glintCfg, cfgErr := config.Load(rootDir)
if cfgErr != nil {
fmt.Fprintf(os.Stderr, "%s: [warning] %s: %v\n", path, config.Filename, cfgErr)
}
fetcherToken := *token
if fetcherToken == "" {
fetcherToken = glintCfg.Token
}
fetcherURL := *gitlabURL
if fetcherURL == "" {
fetcherURL = glintCfg.URL
}
resolvedCacheDir := *cacheDir
if resolvedCacheDir == "" {
resolvedCacheDir = glintCfg.CacheDir
}
if *offline && resolvedCacheDir == "" {
resolvedCacheDir = defaultCacheDir()
}
resolvedProxy := *proxy
if resolvedProxy == "" {
resolvedProxy = glintCfg.Proxy
}
cfg := fetcher.AutoConfig().WithOverrides(*gitlabURL, *token, resolvedCacheDir, *offline)
cfg := fetcher.AutoConfig().WithOverrides(fetcherURL, fetcherToken, resolvedCacheDir, *offline).WithProxy(resolvedProxy)
p, err := model.Parse(path)
if err != nil {
@@ -574,7 +601,6 @@ Examples:
return
}
rootDir := filepath.Dir(filepath.Clean(path))
resolver.ResolveIncludes(p, cfg, rootDir) //nolint:errcheck
resolver.Resolve(p) //nolint:errcheck