Skip to content

Add built-in system project support#647

Open
ptone wants to merge 4 commits into
GoogleCloudPlatform:mainfrom
ptone:scion/system-project
Open

Add built-in system project support#647
ptone wants to merge 4 commits into
GoogleCloudPlatform:mainfrom
ptone:scion/system-project

Conversation

@ptone

@ptone ptone commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

Adds an optional, built-in "system" project that gives Hub admins a dedicated helper-assistant workspace with elevated permissions.

Phases implemented:

  • Phase 1 — Foundation: Reserved system slug, idempotent project registration at startup, configurable workspace path, members group + policy bindings, private visibility with system labels
  • Phase 2 — CLI Mode Elevation: System-project agents receive SCION_CLI_MODE=assistant on all dispatch paths (create, start, restart)
  • Phase 3 — Admin Identity Delegation: System-project agents whose origin user is a current admin get admin-equivalent Hub API access. Demotion of the origin user revokes access immediately (live role lookup)

@google-cla

google-cla Bot commented Jul 9, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the optional built-in "System Project" workspace, providing Hub administrators with elevated management capabilities through ancestry-based admin delegation, startup registration, and reserved slug/label enforcement. The review feedback highlights critical areas for improvement, including caching database lookups on authorization checks to prevent performance bottlenecks, validating user status before granting delegated admin permissions, resolving usability issues with auto-generated reserved slugs for non-admins, and ensuring workspace paths are updated for existing providers.

Comment thread pkg/hub/authz.go
Comment on lines +227 to +237
func (a *AuthzService) isSystemProjectAgent(ctx context.Context, agent AgentIdentity) bool {
projectID := agent.ProjectID()
if projectID == "" {
return false
}
project, err := a.store.GetProject(ctx, projectID)
if err != nil || project == nil {
return false
}
return project.Labels[projectcompat.LabelSystemProject] == "true"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Performance Bottleneck: Database Query on Every Authorization Check

The isSystemProjectAgent method is called at the beginning of checkAccessForAgent for every single authorization check across all agents. Performing a database query (a.store.GetProject) on every check will introduce a significant performance bottleneck, especially under high load or during batch policy evaluations.

Since the system project's ID and labels are static after startup, we should cache this lookup (e.g., using a thread-safe cache like sync.Map or a cache field on the AuthzService struct) to avoid redundant database roundtrips.

Comment thread pkg/hub/authz.go
Comment on lines +239 to +249
func (a *AuthzService) originUserIsCurrentAdmin(ctx context.Context, agent AgentIdentity) bool {
originUserID := agent.OriginUserID()
if originUserID == "" {
return false
}
user, err := a.store.GetUser(ctx, originUserID)
if err != nil || user == nil {
return false
}
return user.Role == "admin"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Security Vulnerability: Missing User Status Check for Admin Delegation

The originUserIsCurrentAdmin method checks if the origin user has the "admin" role, but it does not verify if the user's status is active. If an admin user is suspended, deactivated, or deleted, their associated system-project agents would still retain elevated admin-equivalent permissions.

To prevent unauthorized access, ensure that the user's status is also validated (e.g., user.Status == "active") before granting delegated admin permissions.

Suggested change
func (a *AuthzService) originUserIsCurrentAdmin(ctx context.Context, agent AgentIdentity) bool {
originUserID := agent.OriginUserID()
if originUserID == "" {
return false
}
user, err := a.store.GetUser(ctx, originUserID)
if err != nil || user == nil {
return false
}
return user.Role == "admin"
}
func (a *AuthzService) originUserIsCurrentAdmin(ctx context.Context, agent AgentIdentity) bool {
originUserID := agent.OriginUserID()
if originUserID == "" {
return false
}
user, err := a.store.GetUser(ctx, originUserID)
if err != nil || user == nil {
return false
}
return user.Role == "admin" && user.Status == "active"
}

Comment thread pkg/hub/handlers.go Outdated
Comment on lines +3870 to +3875
if baseSlug == "" {
baseSlug = api.Slugify(req.Name)
}
if s.rejectReservedProjectSlugForNonAdmin(w, ctx, baseSlug) {
return
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Usability Issue: Auto-Generated Reserved Slugs Rejected for Non-Admins

If a non-admin user creates a project named "System" or "Global" without explicitly specifying a slug, baseSlug is auto-generated as "system" or "global". This triggers rejectReservedProjectSlugForNonAdmin and rejects the request with a 403 Forbidden error, preventing non-admins from using these common names.

To improve usability, we should automatically append a suffix (e.g., "-project") to the auto-generated slug if it conflicts with a reserved slug, while still rejecting explicitly requested reserved slugs.

Suggested change
if baseSlug == "" {
baseSlug = api.Slugify(req.Name)
}
if s.rejectReservedProjectSlugForNonAdmin(w, ctx, baseSlug) {
return
}
if baseSlug == "" {
baseSlug = api.Slugify(req.Name)
if projectcompat.IsReservedProjectSlug(baseSlug) {
baseSlug += "-project"
}
} else if s.rejectReservedProjectSlugForNonAdmin(w, ctx, baseSlug) {
return
}

Comment thread pkg/hub/handlers.go Outdated
Comment on lines +4599 to +4602
baseSlug := api.Slugify(req.Name)
if s.rejectReservedProjectSlugForNonAdmin(w, ctx, baseSlug) {
return
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Usability Issue: Auto-Generated Reserved Slugs Rejected in Project Registration

Similar to the project creation endpoint, registering a project named "System" or "Global" will auto-generate a reserved slug and reject the request for non-admin users.

We should automatically append a suffix to the auto-generated slug for non-admin users to allow them to register projects with these names.

Suggested change
baseSlug := api.Slugify(req.Name)
if s.rejectReservedProjectSlugForNonAdmin(w, ctx, baseSlug) {
return
}
baseSlug := api.Slugify(req.Name)
if projectcompat.IsReservedProjectSlug(baseSlug) {
if user := GetUserIdentityFromContext(ctx); user == nil || user.Role() != "admin" {
baseSlug += "-project"
}
}
if s.rejectReservedProjectSlugForNonAdmin(w, ctx, baseSlug) {
return
}

Comment thread cmd/server_broker.go
Comment on lines +211 to +226
provider := &store.ProjectProvider{
ProjectID: project.ID,
BrokerID: brokerID,
BrokerName: brokerName,
LocalPath: workspacePath,
Status: store.BrokerStatusOnline,
LastSeen: time.Now(),
}
if err := s.AddProjectProvider(ctx, provider); err != nil {
if !errors.Is(err, store.ErrAlreadyExists) {
return fmt.Errorf("failed to add system project provider: %w", err)
}
if err := s.UpdateProviderStatus(ctx, project.ID, brokerID, store.BrokerStatusOnline); err != nil {
log.Printf("Warning: failed to update system project provider status: %v", err)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Potential Configuration Mismatch: Workspace Path Not Updated for Existing Providers

If the system project provider already exists, s.AddProjectProvider fails with store.ErrAlreadyExists and we only update the status to store.BrokerStatusOnline. However, if the administrator has changed the workspace_path in the configuration, the stored LocalPath in the database will remain outdated.

Consider updating the provider's LocalPath if it already exists, or logging a warning if the configured workspace path differs from the one stored in the database.

@ptone ptone force-pushed the scion/system-project branch from d06b452 to 26d2d97 Compare July 9, 2026 23:52
Add built-in system project for hub administration with workspace
management, authorization rules, and runtime broker integration.
@ptone ptone force-pushed the scion/system-project branch from a64b697 to bdaa18f Compare July 10, 2026 20:35
Scion Agent (sp-fix-climode) added 3 commits July 10, 2026 22:08
…vider path, tests, slug guard)

- authz.go: replace sync.Once with sync.Mutex + success flag so failed
  system-project lookups are retried on next call
- controlchannel.go: move connected=true to after waitForConnected()
  succeeds, fixing regression where connected was set before handshake
- server_broker.go: replace non-atomic RemoveProjectProvider+AddProjectProvider
  with error-level log when workspace path differs
- authz_test.go: add test verifying suspended admin user is denied
  system-project delegation
- handlers_projects_core.go: remove reserved-slug guard from existing-project
  re-registration path so idempotent re-registration works
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant