Conversation
CurrentTime was captured once via collectEnvironmentInfo() at runner start and the resulting EnvInfo was sent only on the first heartbeat (envInfoSent gate), so a long-lived runner kept reporting its boot time forever. Safari then served that stale value via the `now` tool. Static info (OS, arch, hostname, timezone) is fine to send once; current time is by definition not static. Drop the field entirely — safari uses its own wall clock now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cloud sandbox pool pods spend their entire pre-claim lifetime dialing
safari and getting 401 (no t_cloud_sandbox_0 row exists yet — claim
inserts it). Two timing knobs were tuned for long-lived BYOC outage
recovery and hurt cloud claim latency:
- initialReconnectDelay 1s -> 200ms: first dials race the row INSERT.
The 1s delay added 1-2s of extra 401-retry cost before hostname
auth could possibly succeed.
- maxReconnectDelay 5min -> 10s: a pool pod waiting >50s slid into a
32-64s exponential-backoff sleep, and got declared unrecoverable by
safari's 90s claim window before its next dial — observed live
(sbx_bzQ... did not come online within 1m30s, traceid
6996857e714cb99c31665d043ed3c260). 10s stays well inside the claim
window and remains acceptable for BYOC server-restart recovery.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…NOENT safePath defense-in-depth — runner cannot rely on safari to filter rel_paths that mirror the workspace root. Joining e.root with such a path silently created a nested duplicate workspace tree (matching nested copies were observed on a long-running BYOC host). Reject them at the runner boundary. Read now appends a bounded sibling-name list when stat returns ENOENT, so the agent can self-correct a near-miss filename without spending a turn on an extra list call. .golangci.yml: exclude gosec G706 (log-taint via taint analysis). slog's structured key-value logging is not a format-string injection vector; the rule fires on every slog call with a user-controlled value, which is the whole point of structured logging. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- SyncSkill now checks .checksum+SKILL.md before install (cache hit
returns {Cached:true,Path}; miss with no zip returns {Cached:false}
so cloud retries with zip_data)
- Skills land at <home>/skills/<name>/ instead of <home>/.work/skills/<name>/
- Default home moves from ~/.flashduty-runner/workspace to ~/.flashduty
- FLASHDUTY_RUNNER_HOME is the new canonical override; FLASHDUTY_RUNNER_WORKSPACE
kept as deprecated alias
- Add SyncSkillResult.Cached field (omitempty); SyncSkillArgs unchanged
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- resolveDir(t, dir) replaces duplicated EvalSymlinks boilerplate in ProbeHit and InstallOverwrites tests - errors.Is(err, os.ErrNotExist) replaces deprecated os.IsNotExist pattern Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ructured Runner-side counterparts of fc-safari's 2026-05-02 builtin-tools refactor: - environment/webfetch: SSRF guard via inlined safeHTTPTransport + safeCheckRedirect. Pre-flight validateURL refuses RFC1918 / loopback / link-local / IMDS before any TCP dial; CheckRedirect re-validates each hop so a 302 to 169.254.169.254 is refused. Inlined from go-pkg/x/netsafe (open-source repo cannot import internal modules). - environment/htmlx: HTML-to-markdown / text helpers inlined from go-pkg/x/htmlx for the same reason. html-to-markdown is now a direct dep. - environment/environment::unzipSkill: zip-slip closed — compares abs target against dest+separator and rejects names containing '..'. - environment/environment::grepWithGo: was strings.Contains (literal) — now compiles regex, falls back with regex_compile_error surfaced. Default ignore (.git, node_modules, .flashduty) added; Scanner buffer raised to 1 MiB; rg exit-2 surfaces real I/O errors instead of silent "no matches"; pattern starting with '-' gets '--' prefix; rsplit on ':' so paths containing ':' parse cleanly. - protocol.GrepArgs gains output_mode, context_before/after, head_limit, file_type, case_sensitive — forwarded to ripgrep flags. - environment/environment::Bash: per-stream large-output (stderr now truncates to its own bash_stderr_*.txt); LimitedWriter honors the io.Writer contract (returns ErrOutputCapped at the cap, exposes Hit(), appends "[output capped at 10MB]" marker once). - protocol.BashResult carries truncated_stdout/stderr, stdout_file_path/stderr_file_path, *_total_size; legacy fields remain populated so old safari clients keep working. - environment/large_output::ShouldSkipForOutputsDir: substring match → word-boundary regex (no more 'thread '/'head ' false positives). WriteRaw lowercased to writeRaw (single internal caller). - permission/permission: AST walk now visits CmdSubst / ProcSubst / nested Stmts so cat <(curl evil) and echo $(curl evil) hit the same rules. Redirect targets are checked instead of discarded (cmd > /etc/passwd refused). Canonical-form normalization on both rule and command sides (kubectl get pods matches kubectl get *); env-prefix dual evaluation (KUBECONFIG=x kubectl get pods still matches kubectl get *). Rules sort by literal-prefix specificity, first match wins. SafeReadOnlyRules drops echo */find * — find -delete / find -exec rm now denied by default; replaced with scoped allow patterns. E2E verified live: bash returns the new structured shape, web (action=fetch) reaches example.com via the safe transport, all 17+ new permission/permission_test scenarios pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- netsafe: guard http.DefaultTransport type-assertion (errcheck) - environment: switch if-else chain to switch (gocritic), drop dead err=nil (ineffassign), rename shadowed err vars (govet) - permission: syntax.ClbOut → syntax.RdrClob (staticcheck SA1019) - cmd: add /health listener for Tencent AGS readiness probe; bind via ListenConfig.Listen with parent context (noctx) and document the all-interfaces bind (gosec G102) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d04c4ac to
0772182
Compare
…n windows - .golangci.yml: pinned CI golangci-lint v2.4 rejects G706 under gosec.excludes (rule unknown to its gosec). Move suppression to the version-safe linters.exclusions.rules text match so both v2.4 and newer versions stay quiet. Same regression previously fixed in a60df85. - environment/unzipSkill: filepath.IsAbs("/etc/passwd") returns false on Windows (no drive letter), so the absolute-path guard let the entry slip through and TestUnzipSkill_RejectsZipSlip/absolute-path-unix failed on windows-latest. Reject leading "/" or "\\" on the raw name before Clean normalizes separators. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Brief description of what this PR does.
Related Issue
Closes #
Type of Change
Checklist
make fmtandmake lint