T09 · Insecure Skill Coding Practices
- Location
internal/api/client.go:88- Finding
Authentication Secrets Can Be Forwarded Across Unrestricted HTTP Redirects
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This appears to be a real Speediance CLI skill, but it needs review because it handles reusable account credentials and has concrete credential-handling weaknesses.
Install only if you are comfortable giving this CLI your Speediance email/password and allowing it to read workout data and create programs on your account. Prefer environment variables or a password manager over config.json/.env, keep token-cache paths private, use dry-run before push, and pin a known release instead of installing a moving latest version.
internal/api/client.go:88Authentication Secrets Can Be Forwarded Across Unrestricted HTTP Redirects
internal/cli/config.go:131Reusable Account Password Can Be Persisted in Plaintext Configuration
The declared description presents a full-featured Speediance cloud CLI with account authentication, local token caching, outbound HTTPS requests, and data operations on workouts, exercise catalogs, and training programs. The supplied code chunk does none of that. It only defines a Cobra version command that returns build/version metadata in text or JSON. This is a materially different and much narrower behavior than the declared purpose. While a version subcommand could exist within such a tool, this specific chunk does not substantiate the declared capabilities, so the description does not accurately represent what this supplied code chunk actually does.
The skill is designed to read credentials from environment variables, config.json, and a local .env file, and to persist a session token on disk. While this is common for CLI tools, it creates a real credential-handling risk: secrets may be exposed through overly broad file access, unsafe file permissions, accidental inclusion in working directories, or theft of the cached token by other local processes.
- "Speediance cloud API (HTTPS) — authentication, workout history, exercise catalog, program creation"
files.read:
- "config.json — optional credential/config file (working directory, or your OS user-config dir)"
- ".env — optional env file in the working directory; only the documented SPEEDIANCE_* keys are read (parsed to a map, never exported into the process environment)"
- "token cache — cached session token in your OS user-cache dir by default (non-roaming); path overridable via SPEEDIANCE_TOKEN_CACHE or the token_cache_path config key. A legacy .token.json in the working directory is read once to migrate it."
- "plan JSON files passed to the push command"
files.write:
Referenced artifact was not completely inspected
Every run saves the full catalog to `--out` (default `library.json`) — `--search`
Referenced artifact was not completely inspected
Every run saves the full catalog to `--out` (default `library.json`) — `--search`
The code automatically reads a .env file from the current working directory, which is attacker-influenceable in agent or automation contexts. Although only SPEEDIANCE_* keys are consumed, a malicious .env can still silently inject service credentials or redirect configuration such as SPEEDIANCE_CONFIG or SPEEDIANCE_TOKEN_CACHE, causing the tool to act under attacker-chosen account context or write sensitive token material to an unsafe path.
// a stray or hostile .env could escalate into the tool's runtime. godotenv.Read
// returns the file as a map and changes no global state, so unknown keys are
// never applied. The documented feature is preserved: SPEEDIANCE_* values in
// .env still act as the env layer. A missing/unreadable .env yields a nil map
// and is a silent no-op.
dotenv, _ := godotenv.Read()
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
Chdir(dir)
clearEnv(t)
// Record the pre-Load state of two real, dangerous knobs so we can prove
// Load left them exactly as they were rather than adopting the .env values.
const ldKey, pathProbe = "LD_PRELOAD", "DOTENV_FOREIGN_SENTINEL_SHOULD_BE_IGNORED"
ldBefore, ldSet := os.LookupEnv(ldKey)
if err := os.WriteFile(filepath.Join(dir, ".env"), []byte(
"SPEEDIANCE_EMAIL=ok@b.com\n"+
"LD_PRELOAD=/tmp/evil.so\n"+
"PATH=/evil/bin\n"+
pathProbe+"=injected\n"), 0o600); err != nil {
t.Fatal(err)
}
cfg, err := Load(Options{})
if err != nil {
t.Fatal(err)
}
// The documented feature still works: SPEEDIANCE_* keys are applied.
if cfg.Email != "ok@b.com" {
t.Errorf("email = %q, want ok@b.com (.env SPEEDIANCE_* must still load)", cfg.Email)
}
// The guard: none of the foreign keys leaked into the process environment.
if v, ok := os.LookupEnv(pathProbe); ok {
t.Errorf("foreign key %s leaked into process env as %q; .env must not mutate the environment", pathPr
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// files.write list, so the published skill never under-declares a capability.
// It asserts the negative — a real file access MISSING from the block fails
// loudly — the exact drift that previously left the `config set` write of
// config.json and the working-directory .env read unadvertised.
func TestSkillDocAdvertisesFileAccess(t *testing.T) {
skill, err := os.ReadFile(filepath.Join(repoRoot(t), "SKILL.md"))
if err != nil {
No suspicious patterns detected.