Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ FROM golang:latest AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
RUN apt-get update && apt-get install -y make
COPY . .
RUN apt-get update && apt-get install -y make && make linux
RUN make linux

FROM debian:bookworm-slim
COPY --from=builder /src/bin/linux/askgod-server /askgod-server
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,10 @@ This will create two executables in `./bin/linux`: `askgod` and `askgod-server`.

```bash
./bin/linux/askgod-server ./askgod.yaml.example
```
```

## MCP Server

The askgod server supports an MCP server at `<askgod_server_address>/mcp`.
This MCP server allows users to submit flags.
The MCP Server is disabled by default, but can be enabled by setting `mcp: true` in the config.
1 change: 1 addition & 0 deletions api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Config struct {

Daemon ConfigDaemon `json:"daemon" yaml:"daemon"`
Database ConfigDatabase `json:"database" yaml:"database"`
MCP bool `json:"mcp" yaml:"mcp"`
}

// ConfigPut represents the editable Askgod configuration.
Expand Down
4 changes: 3 additions & 1 deletion api/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Flag struct {
Description string `json:"description" yaml:"description"`
ReturnString string `json:"return_string" yaml:"return_string"`
Value int64 `json:"value" yaml:"value"`
AIAgent bool `json:"ai_agent" yaml:"ai_agent"`
SubmitTime time.Time `json:"submit_time" yaml:"submit_time"`
}

Expand All @@ -27,7 +28,8 @@ type FlagPut struct {
type FlagPost struct {
FlagPut `yaml:",inline"`

Flag string `json:"flag" yaml:"flag"`
Flag string `json:"flag" yaml:"flag"`
AIAgent bool `json:"ai_agent" yaml:"ai_agent"`
}

// URL: /1.0/flags
Expand Down
6 changes: 4 additions & 2 deletions api/score.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type AdminScore struct {
AdminScorePost `yaml:",inline"`

ID int64 `json:"id" yaml:"id"`
AIAgent bool `json:"ai_agent" yaml:"ai_agent"`
SubmitTime time.Time `json:"submit_time" yaml:"submit_time"`
}

Expand All @@ -25,6 +26,7 @@ type AdminScorePut struct {
type AdminScorePost struct {
AdminScorePut `yaml:",inline"`

TeamID int64 `json:"team_id" yaml:"team_id"`
FlagID int64 `json:"flag_id" yaml:"flag_id"`
TeamID int64 `json:"team_id" yaml:"team_id"`
FlagID int64 `json:"flag_id" yaml:"flag_id"`
AIAgent bool `json:"ai_agent" yaml:"ai_agent"`
}
3 changes: 3 additions & 0 deletions askgod.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,6 @@ subnets:
guests:
- ::/0
- 0.0.0.0/0

# Enable or disable the MCP server. Defaults to false.
mcp: true
3 changes: 2 additions & 1 deletion cmd/askgod/cmd_admin_score.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (c *client) cmdAdminListScores(ctx context.Context, _ *cli.Command) error {
const layout = "2006/01/02 15:04"

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ID", "TeamID", "FlagID", "Value", "Submit time", "Notes"})
table.SetHeader([]string{"ID", "TeamID", "FlagID", "Value", "Submit time", "AI Agent", "Notes"})
table.SetBorder(false)
table.SetAutoWrapText(false)

Expand All @@ -121,6 +121,7 @@ func (c *client) cmdAdminListScores(ctx context.Context, _ *cli.Command) error {
strconv.FormatInt(entry.FlagID, 10),
strconv.FormatInt(entry.Value, 10),
entry.SubmitTime.Local().Format(layout),
strconv.FormatBool(entry.AIAgent),
entry.Notes,
})
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/askgod/cmd_history.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (c *client) cmdHistory(ctx context.Context, cmd *cli.Command) error {
const layout = "2006/01/02 15:04"

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ID", "Description", "Value", "Timestamp", "Message", "Notes"})
table.SetHeader([]string{"ID", "Description", "Value", "Timestamp", "AI Agent", "Message", "Notes"})
table.SetBorder(false)
table.SetAutoWrapText(false)

Expand All @@ -60,6 +60,7 @@ func (c *client) cmdHistory(ctx context.Context, cmd *cli.Command) error {
flag.Description,
strconv.FormatInt(flag.Value, 10),
flag.SubmitTime.Local().Format(layout),
strconv.FormatBool(flag.AIAgent),
flag.ReturnString,
flag.Notes,
})
Expand Down
31 changes: 31 additions & 0 deletions cmd/askgod/cmd_submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,34 @@ package main
import (
"context"
"fmt"
"os"

"github.com/urfave/cli/v3"

"github.com/nsec/askgod/api"
)

func hasAIAgentInEnvVariable() bool {
for _, env := range []string{
"AGENT",
"ANTIGRAVITY_AGENT",
"CLAUDECODE",
"CLAUDE_CODE_IS_COWORK",
"CLINE_ACTIVE",
"CODEX_SHELL",
"CURSOR_AGENT",
"GEMINI_CLI",
"OPENCODE",
"WINDSURF_CASCADE_TERMINAL_KIND",
} {
if os.Getenv(env) != "" {
return true
}
}

return false
}

func (c *client) cmdSubmit(ctx context.Context, cmd *cli.Command) error {
if cmd.NArg() != 1 {
_ = cli.ShowCommandHelp(ctx, cmd, "submit")
Expand All @@ -20,6 +42,15 @@ func (c *client) cmdSubmit(ctx context.Context, cmd *cli.Command) error {
flag := api.FlagPost{}
flag.Flag = cmd.Args().Get(0)
flag.Notes = cmd.String("notes")
flag.AIAgent = cmd.Bool("agent")

if !cmd.Bool("no-ai-autodetect") && !flag.AIAgent {
flag.AIAgent = hasAIAgentInEnvVariable()
}

if flag.AIAgent {
_, _ = fmt.Print("Note: This flag submission is marked as AI-assisted.\n") //nolint:forbidigo
}

// Send the flag
resp := api.Flag{}
Expand Down
9 changes: 9 additions & 0 deletions cmd/askgod/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,15 @@ func main() {
Name: "notes",
Usage: "Some notes to remind you of the flag",
},
&cli.BoolFlag{
Name: "agent",
Usage: "Flag was found mostly or entirely with an AI agent",
},
&cli.BoolFlag{
Name: "no-ai-autodetect",
Sources: cli.EnvVars("ASKGOD_DISABLE_AGENT_AUTODETECT"),
Usage: "Disable automatic AI agent detection",
},
},
Action: c.cmdSubmit,
},
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ services:
volumes:
- ${PWD}/askgod.yaml:/askgod.yaml
command: /askgod.yaml
restart: on-failure
depends_on:
- database

Expand Down
28 changes: 14 additions & 14 deletions internal/database/db_scores.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (db *DB) GetTeamFlags(ctx context.Context, teamid int64) ([]api.Flag, error
resp := []api.Flag{}

// Query all the scores from the database
rows, err := db.QueryContext(ctx, "SELECT score.flagid, flag.description, score.value, score.notes, score.submit_time, flag.return_string FROM score LEFT JOIN flag ON flag.id=score.flagid WHERE score.teamid=$1 ORDER BY score.submit_time ASC;", teamid)
rows, err := db.QueryContext(ctx, "SELECT score.flagid, flag.description, score.value, score.notes, score.ai_agent, score.submit_time, flag.return_string FROM score LEFT JOIN flag ON flag.id=score.flagid WHERE score.teamid=$1 ORDER BY score.submit_time ASC;", teamid)
if err != nil {
return nil, err
}
Expand All @@ -40,7 +40,7 @@ func (db *DB) GetTeamFlags(ctx context.Context, teamid int64) ([]api.Flag, error
for rows.Next() {
row := api.Flag{}

err := rows.Scan(&row.ID, &row.Description, &row.Value, &row.Notes, &row.SubmitTime, &row.ReturnString)
err := rows.Scan(&row.ID, &row.Description, &row.Value, &row.Notes, &row.AIAgent, &row.SubmitTime, &row.ReturnString)
if err != nil {
return nil, err
}
Expand All @@ -63,8 +63,8 @@ func (db *DB) GetTeamFlag(ctx context.Context, teamid int64, id int64) (*api.Fla
resp := api.Flag{}

// Query all the scores from the database
err := db.QueryRowContext(ctx, "SELECT score.flagid, flag.description, score.value, score.notes, score.submit_time, flag.return_string FROM score LEFT JOIN flag ON flag.id=score.flagid WHERE score.teamid=$1 AND score.flagid=$2 ORDER BY score.submit_time ASC;", teamid, id).Scan(
&resp.ID, &resp.Description, &resp.Value, &resp.Notes, &resp.SubmitTime, &resp.ReturnString)
err := db.QueryRowContext(ctx, "SELECT score.flagid, flag.description, score.value, score.notes, score.ai_agent, score.submit_time, flag.return_string FROM score LEFT JOIN flag ON flag.id=score.flagid WHERE score.teamid=$1 AND score.flagid=$2 ORDER BY score.submit_time ASC;", teamid, id).Scan(
&resp.ID, &resp.Description, &resp.Value, &resp.Notes, &resp.AIAgent, &resp.SubmitTime, &resp.ReturnString)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -124,17 +124,17 @@ func (db *DB) SubmitTeamFlag(ctx context.Context, teamid int64, flag api.FlagPos
// Add the flag
id = -1

err = db.QueryRowContext(ctx, "INSERT INTO score (teamid, flagid, value, notes, submit_time) VALUES ($1, $2, $3, $4, $5) RETURNING id;",
teamid, row.ID, row.Value, flag.Notes, time.Now()).Scan(&id)
err = db.QueryRowContext(ctx, "INSERT INTO score (teamid, flagid, value, notes, submit_time, ai_agent) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id;",
teamid, row.ID, row.Value, flag.Notes, time.Now(), flag.AIAgent).Scan(&id)
if err != nil {
return nil, nil, err
}

// Query the new entry
result := api.Flag{}

err = db.QueryRowContext(ctx, "SELECT score.flagid, flag.description, score.value, score.notes, score.submit_time, flag.return_string FROM score LEFT JOIN flag ON flag.id=score.flagid WHERE score.id=$1;", id).Scan(
&result.ID, &result.Description, &result.Value, &result.Notes, &result.SubmitTime, &result.ReturnString)
err = db.QueryRowContext(ctx, "SELECT score.flagid, flag.description, score.value, score.notes, score.ai_agent, score.submit_time, flag.return_string FROM score LEFT JOIN flag ON flag.id=score.flagid WHERE score.id=$1;", id).Scan(
&result.ID, &result.Description, &result.Value, &result.Notes, &result.AIAgent, &result.SubmitTime, &result.ReturnString)
if err != nil {
return nil, nil, err
}
Expand All @@ -148,7 +148,7 @@ func (db *DB) GetScores(ctx context.Context) ([]api.AdminScore, error) {
resp := []api.AdminScore{}

// Query all the scores from the database
rows, err := db.QueryContext(ctx, "SELECT id, teamid, flagid, value, notes, submit_time FROM score ORDER BY id ASC;")
rows, err := db.QueryContext(ctx, "SELECT id, teamid, flagid, value, notes, ai_agent, submit_time FROM score ORDER BY id ASC;")
if err != nil {
return nil, err
}
Expand All @@ -158,7 +158,7 @@ func (db *DB) GetScores(ctx context.Context) ([]api.AdminScore, error) {
for rows.Next() {
row := api.AdminScore{}

err := rows.Scan(&row.ID, &row.TeamID, &row.FlagID, &row.Value, &row.Notes, &row.SubmitTime)
err := rows.Scan(&row.ID, &row.TeamID, &row.FlagID, &row.Value, &row.Notes, &row.AIAgent, &row.SubmitTime)
if err != nil {
return nil, err
}
Expand All @@ -180,8 +180,8 @@ func (db *DB) GetScore(ctx context.Context, id int64) (*api.AdminScore, error) {
// Query the database entry
row := api.AdminScore{}

err := db.QueryRowContext(ctx, "SELECT id, teamid, flagid, value, notes, submit_time FROM score WHERE id=$1;", id).Scan(
&row.ID, &row.TeamID, &row.FlagID, &row.Value, &row.Notes, &row.SubmitTime)
err := db.QueryRowContext(ctx, "SELECT id, teamid, flagid, value, notes, ai_agent, submit_time FROM score WHERE id=$1;", id).Scan(
&row.ID, &row.TeamID, &row.FlagID, &row.Value, &row.Notes, &row.AIAgent, &row.SubmitTime)
if err != nil {
return nil, err
}
Expand All @@ -194,8 +194,8 @@ func (db *DB) CreateScore(ctx context.Context, score api.AdminScorePost) (int64,
id := int64(-1)

// Create the database entry
err := db.QueryRowContext(ctx, "INSERT INTO score (teamid, flagid, value, notes, submit_time) VALUES ($1, $2, $3, $4, $5) RETURNING id",
score.TeamID, score.FlagID, score.Value, score.Notes, time.Now()).Scan(&id)
err := db.QueryRowContext(ctx, "INSERT INTO score (teamid, flagid, value, notes, ai_agent, submit_time) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id",
score.TeamID, score.FlagID, score.Value, score.Notes, score.AIAgent, time.Now()).Scan(&id)
if err != nil {
return -1, err
}
Expand Down
1 change: 1 addition & 0 deletions internal/database/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ CREATE TABLE IF NOT EXISTS score (
value INTEGER NOT NULL DEFAULT 0,
submit_time TIMESTAMP WITH TIME ZONE,
notes VARCHAR,
ai_agent BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (teamid) REFERENCES team (id) ON DELETE CASCADE,
FOREIGN KEY (flagid) REFERENCES flag (id) ON DELETE CASCADE,
UNIQUE(teamid, flagid)
Expand Down
7 changes: 7 additions & 0 deletions internal/database/updates.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
var dbUpdates = []dbUpdate{
{version: 1, run: dbUpdateFromV0},
{version: 2, run: dbUpdateFromV1},
{version: 3, run: dbUpdateFromV2},
}

type dbUpdate struct {
Expand Down Expand Up @@ -51,3 +52,9 @@ CREATE TABLE IF NOT EXISTS config (

return err
}

func dbUpdateFromV2(ctx context.Context, _, _ int, db *DB) error {
_, err := db.ExecContext(ctx, "ALTER TABLE score ADD COLUMN ai_agent BOOLEAN NOT NULL DEFAULT FALSE;")

return err
}
Loading
Loading